UNPKG

lively.vm

Version:

Controlled JavaScript code execution and instrumentation.

171 lines (142 loc) 7.64 kB
import { arr, Path } from "lively.lang"; import { parse, stringify, transform, query, nodes } from "lively.ast"; import { capturing } from "lively.source-transform"; import { runtime as classRuntime } from "lively.classes"; import { getGlobal } from "./util.js"; var {id, literal, member, objectLiteral} = nodes; export const defaultDeclarationWrapperName = "lively.capturing-declaration-wrapper", defaultClassToFunctionConverterName = "initializeES6ClassForLively"; function processInlineCodeTransformOptions(parsed, options) { if (!parsed.comments) return options; var livelyComment = parsed.comments.find(ea => ea.text.startsWith("lively.vm ")); if (!livelyComment) return options; try { var inlineOptions = eval("inlineOptions = {" + livelyComment.text.slice("lively.vm ".length) + "};"); return Object.assign(options, inlineOptions); } catch (err) { return options; } } export function evalCodeTransform(code, options) { // variable declaration and references in the the source code get // transformed so that they are bound to `varRecorderName` aren't local // state. THis makes it possible to capture eval results, e.g. for // inspection, watching and recording changes, workspace vars, and // incrementally evaluating var declarations and having values bound later. // 1. Allow evaluation of function expressions and object literals code = transform.transformSingleExpression(code); var parsed = parse(code, {withComments: true}); options = processInlineCodeTransformOptions(parsed, options); // 2. Annotate definitions with code location. This is being used by the // function-wrapper-source transform. var {classDecls, funcDecls, varDecls} = query.topLevelDeclsAndRefs(parsed), annotation = {}; if (options.hasOwnProperty("evalId")) annotation.evalId = options.evalId; if (options.sourceAccessorName) annotation.sourceAccessorName = options.sourceAccessorName; [...classDecls, ...funcDecls].forEach(node => node["x-lively-object-meta"] = {...annotation, start: node.start, end: node.end}); varDecls.forEach(node => node.declarations.forEach(decl => decl["x-lively-object-meta"] = {...annotation, start: decl.start, end: decl.end})); // transforming experimental ES features into accepted es6 form... parsed = transform.objectSpreadTransform(parsed); // 3. capture top level vars into topLevelVarRecorder "environment" if (!options.topLevelVarRecorder && options.topLevelVarRecorderName) { let G = getGlobal(); if (options.topLevelVarRecorderName === "GLOBAL") { // "magic" options.topLevelVarRecorder = getGlobal(); } else { options.topLevelVarRecorder = Path(options.topLevelVarRecorderName).get(G); } } if (options.topLevelVarRecorder) { // capture and wrap logic var blacklist = (options.dontTransform || []).concat(["arguments"]), undeclaredToTransform = !!options.recordGlobals ? null/*all*/ : arr.withoutAll(Object.keys(options.topLevelVarRecorder), blacklist), varRecorder = id(options.varRecorderName || '__lvVarRecorder'), es6ClassToFunctionOptions = undefined; if (options.declarationWrapperName || typeof options.declarationCallback === "function") { // 2.1 declare a function that wraps all definitions, i.e. all var // decls, functions, classes etc that get captured will be wrapped in this // function. This allows to define some behavior that is run whenever // variables get initialized or changed as well as transform values. // The parameters passed are: // name, kind, value, recorder // Note that the return value of declarationCallback is used as the // actual value in the code being executed. This allows to transform the // value as necessary but also means that declarationCallback needs to // return sth meaningful! let declarationWrapperName = options.declarationWrapperName || defaultDeclarationWrapperName; options.declarationWrapper = member( id(options.varRecorderName || '__lvVarRecorder'), literal(declarationWrapperName), true); if (options.declarationCallback) options.topLevelVarRecorder[declarationWrapperName] = options.declarationCallback; } var transformES6Classes = options.hasOwnProperty("transformES6Classes") ? options.transformES6Classes : true; if (transformES6Classes) { // Class declarations and expressions are converted into a function call // to `createOrExtendClass`, a helper that will produce (or extend an // existing) constructor function in a way that allows us to redefine // methods and properties of the class while keeping the class object // identical if (!(defaultClassToFunctionConverterName in options.topLevelVarRecorder)) options.topLevelVarRecorder[defaultClassToFunctionConverterName] = classRuntime.initializeClass; es6ClassToFunctionOptions = { currentModuleAccessor: options.currentModuleAccessor, classHolder: varRecorder, functionNode: member(varRecorder, defaultClassToFunctionConverterName), declarationWrapper: options.declarationWrapper, evalId: options.evalId, sourceAccessorName: options.sourceAccessorName }; } // 3.2 Here we call out to the actual code transformation that installs the captured top level vars parsed = capturing.rewriteToCaptureTopLevelVariables( parsed, varRecorder, { es6ImportFuncId: options.es6ImportFuncId, es6ExportFuncId: options.es6ExportFuncId, ignoreUndeclaredExcept: undeclaredToTransform, exclude: blacklist, declarationWrapper: options.declarationWrapper || undefined, classToFunction: es6ClassToFunctionOptions, evalId: options.evalId, sourceAccessorName: options.sourceAccessorName, keepTopLevelVarDecls: options.keepTopLevelVarDecls }); } if (options.wrapInStartEndCall) { parsed = transform.wrapInStartEndCall(parsed, { startFuncNode: options.startFuncNode, endFuncNode: options.endFuncNode }); } var result = stringify(parsed); if (options.sourceURL) result += "\n//# sourceURL=" + options.sourceURL.replace(/\s/g, "_"); return result; } export function evalCodeTransformOfSystemRegisterSetters(code, options = {}) { if (!options.topLevelVarRecorder) return code; if (typeof options.declarationCallback === "function" || options.declarationWrapperName) { let declarationWrapperName = options.declarationWrapperName || defaultDeclarationWrapperName; options.declarationWrapper = member( id(options.varRecorderName), literal(declarationWrapperName), true); if (options.declarationCallback) options.topLevelVarRecorder[declarationWrapperName] = options.declarationCallback; } var parsed = parse(code), blacklist = (options.dontTransform || []).concat(["arguments"]), undeclaredToTransform = !!options.recordGlobals ? null/*all*/ : arr.withoutAll(Object.keys(options.topLevelVarRecorder), blacklist), result = capturing.rewriteToRegisterModuleToCaptureSetters( parsed, id(options.varRecorderName || '__lvVarRecorder'), {exclude: blacklist, ...options}); return stringify(result); } function copyProperties(source, target, exceptions = []) { Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source)) .forEach(name => exceptions.indexOf(name) === -1 && Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name))); }