Skip to content

Commit

Permalink
Support processing optional chaining (close #2714) (#2755)
Browse files Browse the repository at this point in the history
* initial

* update acorn-hammerhead
  • Loading branch information
miherlosev committed Apr 5, 2022
1 parent ee18b91 commit 0461f98
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 23 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -15,7 +15,7 @@
"url": "git+https://github.com/DevExpress/testcafe-hammerhead.git"
},
"dependencies": {
"acorn-hammerhead": "0.5.0",
"acorn-hammerhead": "0.6.1",
"asar": "^2.0.1",
"bowser": "1.6.0",
"brotli": "^1.3.1",
Expand Down
9 changes: 6 additions & 3 deletions src/client/sandbox/code-instrumentation/properties/index.ts
Expand Up @@ -87,12 +87,15 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {
}
};

private static _propertyGetter (owner: any, propName: any) {
if (isNullOrUndefined(owner))
private static _propertyGetter (owner: any, propName: any, optional = false) {
if (isNullOrUndefined(owner) && !optional)
PropertyAccessorsInstrumentation._error(`Cannot read property '${propName}' of ${inaccessibleTypeToStr(owner)}`);

if (typeof propName === 'string' && shouldInstrumentProperty(propName)) {
if (!WindowSandbox.isProxyObject(owner) && PropertyAccessorsInstrumentation._ACCESSORS[propName].condition(owner))
if (optional && isNullOrUndefined(owner))
return void 0;

else if (!WindowSandbox.isProxyObject(owner) && PropertyAccessorsInstrumentation._ACCESSORS[propName].condition(owner))
return PropertyAccessorsInstrumentation._ACCESSORS[propName].get(owner);
}

Expand Down
5 changes: 3 additions & 2 deletions src/processing/script/index.ts
Expand Up @@ -16,7 +16,8 @@ const OBJECT_RE = /^\s*\{.*\}\s*$/;
const TRAILING_SEMICOLON_RE = /;\s*$/;
const OBJECT_WRAPPER_RE = /^\s*\((.*)\);\s*$/;
const SOURCEMAP_RE = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)/gm;
const PROCESSED_SCRIPT_RE = new RegExp([

const PROCESSED_SCRIPT_RE = new RegExp([
reEscape(INSTRUCTION.getLocation),
reEscape(INSTRUCTION.setLocation),
reEscape(INSTRUCTION.getProperty),
Expand All @@ -30,7 +31,7 @@ const PROCESSED_SCRIPT_RE = new RegExp([
const PARSING_OPTIONS = {
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: 11
ecmaVersion: 13
};

// Code pre/post-processing
Expand Down
16 changes: 12 additions & 4 deletions src/processing/script/node-builder.ts
Expand Up @@ -170,16 +170,24 @@ export function createMethodCallWrapper (owner: Expression, method: Literal, arg
return createSimpleCallExpression(callMethodIdentifier, [owner, method, methodArgsArray]);
}

export function createPropertyGetWrapper (propertyName: string, owner: Expression): CallExpression {
export function createPropertyGetWrapper (propertyName: string, owner: Expression, optional = false): CallExpression {
const getPropertyIdentifier = createIdentifier(INSTRUCTION.getProperty);
const args = [owner, createSimpleLiteral(propertyName)];

return createSimpleCallExpression(getPropertyIdentifier, [owner, createSimpleLiteral(propertyName)]);
if (optional)
args.push(createSimpleLiteral(optional));

return createSimpleCallExpression(getPropertyIdentifier, args);
}

export function createComputedPropertyGetWrapper (property: Expression, owner: Expression): CallExpression {
export function createComputedPropertyGetWrapper (property: Expression, owner: Expression, optional = false): CallExpression {
const getPropertyIdentifier = createIdentifier(INSTRUCTION.getProperty);
const args = [owner, property];

if (optional)
args.push(createSimpleLiteral(optional));

return createSimpleCallExpression(getPropertyIdentifier, [owner, property]);
return createSimpleCallExpression(getPropertyIdentifier, args);
}

export function createComputedPropertySetWrapper (property: Expression, owner: Expression, value: Expression): CallExpression {
Expand Down
Expand Up @@ -55,7 +55,7 @@ const transformer: Transformer<MemberExpression> = {
return true;
},

run: node => createComputedPropertyGetWrapper(node.property, node.object as Expression)
run: node => createComputedPropertyGetWrapper(node.property, node.object as Expression, node.optional)
};

export default transformer;
2 changes: 1 addition & 1 deletion src/processing/script/transformers/property-get.ts
Expand Up @@ -57,7 +57,7 @@ const transformer: Transformer<MemberExpression> = {
},

// eslint-disable-next-line
run: node => createPropertyGetWrapper((node.property as Identifier).name, node.object as Expression)
run: node => createPropertyGetWrapper((node.property as Identifier).name, node.object as Expression, node.optional)
};

export default transformer;
Expand Up @@ -91,7 +91,7 @@ test('the eval method should switch its own context to global', function () {
strictEqual(execScript('window.t3="globalScope";(function(ev){ var t3 = "localScope"; return ev("t3"); })(eval)'), 'globalScope');
});

test('the script processor should process eval\'s global', function () {
test("the script processor should process eval's global", function () {
var link = document.createElement('a');
var testUrl = 'http://host/index.html';

Expand All @@ -102,9 +102,9 @@ test('the script processor should process eval\'s global', function () {
strictEqual(execScript('var ev = eval; ev("test.href")'), testUrl);
});

module('destructuring');

if (!browserUtils.isIE11) {
module('destructuring');

var defaultRestArrayStr = 'var ' + scriptHeader.add('')
.replace(/[\s\S]+(__rest\$Array\s*=\s*function[^}]+})[\s\S]+/g, '$1');
var defaultArrayFromStr = 'var ' + scriptHeader.add('')
Expand Down Expand Up @@ -224,4 +224,38 @@ if (!browserUtils.isIE11) {
test('should process script arg', function () {
strictEqual(execScript('({ a: b } = { a: null }).a'), null);
});

module('others');

test('optional chaining', function () {
var testCases = [
{
src: 'var obj = null; window.optionChainingResult = obj?.href;',
expected: void 0,
},
{
src: 'var obj = { href: "123" }; window.optionChainingResult = obj?.href;',
expected: '123',
},
{
src: 'var obj = null; window.optionChainingResult = obj?.["href"];',
expected: void 0,
},
{
src: 'var obj = { href: "123" }; window.optionChainingResult = obj?.["href"];',
expected: '123',
}
];

for (var i = 0; i < testCases.length; i++) {
var testCase = testCases[i];

eval(processScript(testCase.src));

strictEqual(window.optionChainingResult, testCase.expected);

delete window.optionChainingResult;
delete window.obj;
}
});
}
38 changes: 30 additions & 8 deletions test/server/script-processor-test.js
@@ -1,11 +1,8 @@
const expect = require('chai').expect;
const multiline = require('multiline');
const processScript = require('../../lib/processing/script').processScript;
const isScriptProcessed = require('../../lib/processing/script').isScriptProcessed;
const HEADER = require('../../lib/processing/script/header').HEADER;
const SCRIPT_PROCESSING_START_COMMENT = require('../../lib/processing/script/header').SCRIPT_PROCESSING_START_COMMENT;
const INSTRUMENTED_PROPERTIES = require('../../lib/processing/script/instrumented').PROPERTIES;

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 ACORN_UNICODE_PATCH_WARNING = multiline(function () {/*
ATTENTION! If this test fails, this may happen because you have updated acorn.
Expand Down Expand Up @@ -822,6 +819,31 @@ describe('Script processor', () => {
]);
});

it('Should process optional chaining', () => {
testPropertyProcessing([
{
src: 'obj?.{0}',
expected: '__get$(obj,"{0}",true)',
},
{
src: 'obj?.["{0}"]',
expected: '__get$(obj,"{0}",true)',
},
{
src: 'obj?.{0}?.{0}',
expected: '__get$(__get$(obj,"{0}",true),"{0}",true)'
},
{
src: 'arr?.[0]',
expected: 'arr?.[0]',
},
{
src: 'obj?.{0}?.method(args)',
expected: '__get$(obj,"{0}",true)?.method(args)',
},
]);
});

describe('Destructuring', () => {
it('object pattern declaration', () => {
testProcessing([
Expand Down

0 comments on commit 0461f98

Please sign in to comment.