babel-plugin-prototype-prop-define
Advanced tools
Comparing version 2.2.1 to 2.3.0
{ | ||
"name": "babel-plugin-prototype-prop-define", | ||
"version": "2.2.1", | ||
"version": "2.3.0", | ||
"description": "Transform assignments to properties on built-in prototypes to Object.defineProperty calls", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -13,3 +13,3 @@ <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
### background | ||
### transformation | ||
@@ -32,10 +32,43 @@ If your primordials are frozen, such as in [SES](https://github.com/agoric/ses), assigning keys that are found on the prototype will throw an error. | ||
const x = {} | ||
Object.defineProperty(x, 'toString', { | ||
value: () => 'hello', | ||
writable: true, | ||
enumerable: true, | ||
configurable: true, | ||
}) | ||
(function (parent, key, value) { | ||
Object.defineProperty(parent, key, { | ||
value: value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true | ||
}) | ||
return value; | ||
})(x, "toString", () => 'hello'); | ||
x.toString() | ||
// => 'hello' | ||
``` | ||
The code is transformed into an IIFE in order to ensure the "parent" (`x`), "key" (`y`) and "value" (`() => 'hello'`) statements are evaluated exactly once, and that the whole expression resolves to the value of the "value" statement. This is needed because `Object.defineProperty` calls resolve to `undefined`. | ||
For "computed" assignment, the resulting code is a little uglier. | ||
```js | ||
const x = {}; | ||
const y = 'toString'; | ||
x[y] = () => 'hello' | ||
``` | ||
becomes | ||
```js | ||
const x = {}; | ||
const y = 'toString'; | ||
(function (parent, key, value) { | ||
["constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "toLocaleString", "length", "concat", "find", "findIndex", "pop", "push", "shift", "unshift", "slice", "splice", "includes", "indexOf", "keys", "entries", "forEach", "filter", "map", "every", "some", "reduce", "reduceRight", "join", "reverse", "sort", "lastIndexOf", "copyWithin", "fill", "values", "name", "arguments", "caller", "apply", "bind", "call", "message"].includes(key) ? Object.defineProperty(parent, key, { | ||
value: value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true | ||
}) : parent[key] = value; | ||
return value; | ||
})(x, y, () => true); | ||
``` | ||
Here the check that is normally performed at parse time (checking if the key is in this whitelist), is performed at runtime to determine if normal assignment or `Object.defineProperty` should be used. |
219
src/index.js
@@ -56,2 +56,4 @@ const primordialKeySet = new Set([ | ||
const SKIP_PARSE_FLAG = 'prototype-prop-define-skip' | ||
module.exports = () => | ||
@@ -82,3 +84,3 @@ // { | ||
// internal flag to skip node | ||
if (node.prototypePropDefineSkip) return | ||
if (node[SKIP_PARSE_FLAG]) return | ||
@@ -137,3 +139,3 @@ // ensure basic assignment (not += etc) | ||
const definePropertyExpression = createDefinePropertyExpression( | ||
const definePropertyExpression = createDefinePropertyAndResolveExpression( | ||
parentStatement, | ||
@@ -151,3 +153,7 @@ propertyKeyStatement, | ||
function createDefinePropertyExpression( | ||
// (function(parent, key, value){ | ||
// Object.defineProperty(parent, key, value); | ||
// return value | ||
// })(a, b, c) | ||
function createDefinePropertyAndResolveExpression ( | ||
parentStatement, | ||
@@ -157,2 +163,29 @@ propertyKeyStatement, | ||
) { | ||
const body = [ | ||
createDefinePropertyExpression( | ||
createIdentifier('parent'), | ||
createIdentifier('key'), | ||
createIdentifier('value'), | ||
), | ||
{ | ||
"type": "ReturnStatement", | ||
"argument": createIdentifier('value') | ||
} | ||
] | ||
const args = [ | ||
['parent', parentStatement], | ||
['key', propertyKeyStatement], | ||
['value', valueStatement], | ||
] | ||
return createIife (args, body) | ||
} | ||
// Object.defineProperty(parent, key, value) | ||
function createDefinePropertyExpression ( | ||
parentStatement, | ||
propertyKeyStatement, | ||
valueStatement, | ||
) { | ||
return { | ||
@@ -187,13 +220,10 @@ type: 'CallExpression', | ||
// { value: 1, writable: true, enumerable: true, configurable: true } | ||
function createPropertyDescriptor (valueStatement) { | ||
return { | ||
type: 'ObjectExpression', | ||
// { value: 1, writable: true, enumerable: true, configurable: true } | ||
properties: [ | ||
{ | ||
type: 'ObjectProperty', | ||
key: { | ||
type: 'Identifier', | ||
name: 'value', | ||
}, | ||
key: createIdentifier('value'), | ||
value: valueStatement, | ||
@@ -203,6 +233,3 @@ }, | ||
type: 'ObjectProperty', | ||
key: { | ||
type: 'Identifier', | ||
name: 'writable', | ||
}, | ||
key: createIdentifier('writable'), | ||
value: { | ||
@@ -215,6 +242,3 @@ type: 'BooleanLiteral', | ||
type: 'ObjectProperty', | ||
key: { | ||
type: 'Identifier', | ||
name: 'enumerable', | ||
}, | ||
key: createIdentifier('enumerable'), | ||
value: { | ||
@@ -227,6 +251,3 @@ type: 'BooleanLiteral', | ||
type: 'ObjectProperty', | ||
key: { | ||
type: 'Identifier', | ||
name: 'configurable', | ||
}, | ||
key: createIdentifier('configurable'), | ||
value: { | ||
@@ -241,9 +262,8 @@ type: 'BooleanLiteral', | ||
// (function(target, key, value){ | ||
// (function(parent, key, value){ | ||
// [].includes(key) ? | ||
// DEFINE_PROP : | ||
// target[key] = value; | ||
// Object.defineProperty(parent, key, value) : | ||
// parent[key] = value; | ||
// return value | ||
// })(a, b, c) | ||
function createDynamicAssignmentCheck ( | ||
@@ -254,2 +274,55 @@ parentStatement, | ||
) { | ||
const body = [ | ||
{ | ||
"type": "ExpressionStatement", | ||
"expression": { | ||
"type": "ConditionalExpression", | ||
"test": { | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": createPrimordialKeyArray(), | ||
"property": createIdentifier('includes'), | ||
"computed": false | ||
}, | ||
"arguments": [ | ||
createIdentifier('key'), | ||
] | ||
}, | ||
"consequent": createDefinePropertyExpression( | ||
createIdentifier('parent'), | ||
createIdentifier('key'), | ||
createIdentifier('value'), | ||
), | ||
"alternate": { | ||
"type": "AssignmentExpression", | ||
// prevent this plugin from transforming | ||
[SKIP_PARSE_FLAG]: true, | ||
"operator": "=", | ||
"left": { | ||
"type": "MemberExpression", | ||
"object": createIdentifier('parent'), | ||
"property": createIdentifier('key'), | ||
"computed": true | ||
}, | ||
"right": createIdentifier('value') | ||
} | ||
} | ||
}, | ||
{ | ||
"type": "ReturnStatement", | ||
"argument": createIdentifier('value') | ||
} | ||
] | ||
const args = [ | ||
['parent', parentStatement], | ||
['key', propertyKeyStatement], | ||
['value', valueStatement], | ||
] | ||
return createIife(args, body) | ||
} | ||
function createIife (args, body) { | ||
return { | ||
@@ -262,96 +335,11 @@ "type": "CallExpression", | ||
"async": false, | ||
"params": [ | ||
{ | ||
"type": "Identifier", | ||
"name": "target" | ||
}, | ||
{ | ||
"type": "Identifier", | ||
"name": "key" | ||
}, | ||
{ | ||
"type": "Identifier", | ||
"name": "value" | ||
} | ||
], | ||
"params": args.map(([argName]) => createIdentifier(argName)), | ||
"body": { | ||
"type": "BlockStatement", | ||
"body": [ | ||
{ | ||
"type": "ExpressionStatement", | ||
"expression": { | ||
"type": "ConditionalExpression", | ||
"test": { | ||
"type": "CallExpression", | ||
"callee": { | ||
"type": "MemberExpression", | ||
"object": createPrimordialKeyArray(), | ||
"property": { | ||
"type": "Identifier", | ||
"name": "includes" | ||
}, | ||
"computed": false | ||
}, | ||
"arguments": [ | ||
{ | ||
"type": "Identifier", | ||
"name": "key" | ||
} | ||
] | ||
}, | ||
"consequent": createDefinePropertyExpression( | ||
{ | ||
"type": "Identifier", | ||
"name": "target" | ||
}, | ||
{ | ||
"type": "Identifier", | ||
"name": "key" | ||
}, | ||
{ | ||
"type": "Identifier", | ||
"name": "value" | ||
}, | ||
), | ||
"alternate": { | ||
"type": "AssignmentExpression", | ||
"operator": "=", | ||
"left": { | ||
"type": "MemberExpression", | ||
"object": { | ||
"type": "Identifier", | ||
"name": "target" | ||
}, | ||
"property": { | ||
"type": "Identifier", | ||
"name": "key" | ||
}, | ||
"computed": true | ||
}, | ||
"right": { | ||
"type": "Identifier", | ||
"name": "value" | ||
}, | ||
"prototypePropDefineSkip": true | ||
} | ||
} | ||
}, | ||
{ | ||
"type": "ReturnStatement", | ||
"argument": { | ||
"type": "Identifier", | ||
"name": "value" | ||
} | ||
} | ||
], | ||
body, | ||
"directives": [] | ||
} | ||
}, | ||
"arguments": [ | ||
parentStatement, | ||
propertyKeyStatement, | ||
valueStatement, | ||
] | ||
"arguments": args.map(([_, argValue]) => argValue) | ||
} | ||
} | ||
@@ -369,2 +357,9 @@ | ||
} | ||
} | ||
function createIdentifier (name) { | ||
return { | ||
"type": "Identifier", | ||
name, | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34247
72
377