Skip to content

Commit

Permalink
with statements WIP 2
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Aug 30, 2023
1 parent d02d462 commit 178d6d7
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 19 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,6 @@ NB Applications can *use* any of these within functions, just that instances of
* Unsupported: `export default Promise.resolve();` (Promise instance serialized directly)
* Unsupported: `const p = Promise.resolve(); export default function f() { return p; };` (Promise instance in outer scope of exported function)
`with (...) {...}` is also not supported where it alters the scope of a function being serialized.
### Browser code
This works in part. You can, for example, build a simple React app with Livepack.
Expand Down
3 changes: 0 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# TODO

* Create `with () {}` block in output
* Make sure that scope function is sloppy mode
* Prevent vars below `with () {}` having their names mangled
* Including vars internal to functions
* Deal with `with` in `eval()`
* Tests
* Remove note in README that `with` is not supported
38 changes: 24 additions & 14 deletions lib/serialize/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ module.exports = {
const paramNodes = [];
const {mangle} = this.options;
let hasArgumentsOrEvalParam = false,
frozenThisVarName, frozenArgumentsVarName;
frozenThisVarName, frozenArgumentsVarName, withVarName;
for (const paramName of paramNames) {
let newName;
const injectionVarNode = injectionVarNodes[paramName];
Expand All @@ -632,7 +632,7 @@ module.exports = {

// Rename injection node
renameInjectionVarNode();
} else if (!containsEval) {
} else if (!containsEval || paramName === 'with') { // `with` param is always renamed
newName = transformVarName(paramName);
if (newName !== paramName) {
// Rename all nodes
Expand All @@ -642,6 +642,9 @@ module.exports = {

// Rename injection node
renameInjectionVarNode();

// Record var name for `with` object
if (paramName === 'with') withVarName = newName;
}
} else {
// Frozen var name (potentially used in `eval()`)
Expand Down Expand Up @@ -678,10 +681,11 @@ module.exports = {
// Handle strict/sloppy mode
let isStrict;
if (!isRoot) {
if (hasArgumentsOrEvalParam) {
if (hasArgumentsOrEvalParam || withVarName) {
// If param named `arguments` or `eval`, scope function must be sloppy mode
// or it's a syntax error.
// NB Only way param will be called `arguments` or `eval` is if it's frozen by an `eval()`.
// `with (...)` requires sloppy mode too.
isStrict = false;
} else if (strictFns.length === 0) {
// No strict child functions or child blocks. Block is sloppy if any sloppy children,
Expand Down Expand Up @@ -710,18 +714,18 @@ module.exports = {
returnNode = t.sequenceExpression(internalFunctionNodes);
}

// If uses frozen `this` or `arguments`, wrap return value in an IIFE
// to inject these values as actual `this` / `arguments`.
// `() => eval(x)` -> `(function() { return () => eval(x); }).apply(this$0, arguments$0)`
// TODO: In sloppy mode, it's possible for `arguments` to be re-defined as a non-iterable object
// which would cause an error when this function is called.
// A better solution when outputting sloppy mode code would be to just use a var called `arguments`,
// rather than injecting. Of course this isn't possible in ESM.
// TODO: Ensure scope function using `this` is strict mode if value of `this` is not an object.
// In sloppy mode literals passed as `this` gets boxed.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode#securing_javascript
// TODO: Also doesn't work where `this` or `arguments` is circular and is injected late.
if (frozenThisVarName || frozenArgumentsVarName) {
// Uses frozen `this` or `arguments`.
// Wrap return value in an IIFE to inject these values as actual `this` / `arguments`.
// `() => eval(x)` -> `(function() { return () => eval(x); }).apply(this$0, arguments$0)`
// TODO: In sloppy mode, it's possible for `arguments` to be re-defined as a non-iterable object
// which would cause an error when this function is called.
// A better solution when outputting sloppy mode code would be to just use a var called
// `arguments`, rather than injecting. Of course this isn't possible in ESM.
// TODO: Ensure scope function using `this` is strict mode if value of `this` is not an object.
// In sloppy mode literals passed as `this` gets boxed.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode#securing_javascript
// TODO: Also doesn't work where `this` or `arguments` is circular and is injected late.
const callArgsNodes = [];
let functionNode;
if (frozenThisVarName) {
Expand All @@ -741,6 +745,12 @@ module.exports = {
),
callArgsNodes
);
} else if (withVarName) {
// Wrap in `{ with (with$0) return ...; }`.
// NB: It's not possible for the `with` object to be a circular reference.
returnNode = t.blockStatement([
t.withStatement(t.identifier(withVarName), t.returnStatement(returnNode))
]);
}

const node = t.arrowFunctionExpression(paramNodes, returnNode);
Expand Down

0 comments on commit 178d6d7

Please sign in to comment.