"use strict"; const assert = require("assert"); const types = require("@babel/types"); const template = require("@babel/template").default; const templateExpression = require("./util/template-expression"); const NoChange = require("astformer/actions/no-change"); // FIXME: Auto-generate argument list based on exposed API surface? let tmplModule = template(` module.exports = function({ builtins, removeAttrs, $memoize, $handleArgument, $assertUniqueKeys }) { return %%contents%%; }; `); let tmplIdentifierReference = templateExpression(`( %%name%%() )`); let tmplWithWrapper = templateExpression(` (() => { const %%contextName%% = %%setupCode%%; return %%body%%; })() `); let tmplImplicitContextNested = templateExpression(` Object.assign(Object.create(%%parent%%), %%environment%%) `); let tmplImplicitContextTop = templateExpression(` Object.assign({}, %%environment%%) `); let implicitContextCounter = 0; let trivial = { name: "trivial-transforms", visitors: { // Trivial transforms NixParenthesizedExpression: (node) => node.expression, Program: (_node, { setContext, defer }) => { setContext(null, "identifierType", "reference"); return defer((node) => { assert(node.body.length === 1); return tmplModule({ contents: node.body[0] }); }); }, NixAttributePath: (_node, { setContext }) => { // This ensures that attribute paths get excluded from being processed as an identifier, now that the parser no longer distinguishes between attribute identifiers and regular identifiers setContext([ "attr" ], "identifierType", "attributePath"); return NoChange; }, NixIdentifier: (node, { getContext }) => { // FIXME: Mangle reserved keywords like `const` let safeName = (node.name === "import") ? "$import" : node.name; if (getContext("identifierType") === "define") { return types.identifier(safeName); } else if (getContext("identifierType") === "attributePath") { // Do nothing, this will get removed from the tree in attribute set handling return NoChange; } else { // reference return tmplIdentifierReference({ name: safeName }); } }, NixBinaryOperation: (_node, { defer }) => { return defer((node) => { // FIXME: Verify that all the 'operator' values match between Nix and JS! // FIXME: Need to replace this with utility functions to deal with eg. paths return types.binaryExpression(node.operator, node.left, node.right); }); }, NixWithExpression: (node, { defer, setContext, getContext }) => { // TODO: Can we optimize for the fast case (no nested `with`) by referencing the source attrset directly? let parentContext = getContext("implicitContext"); let hasParent = (parentContext != null); let contextName = `$implicit${implicitContextCounter++}`; setContext(null, "implicitContext", contextName); let setupCode = (hasParent) ? tmplImplicitContextNested({ environment: node.environment, parent: parentContext }) : tmplImplicitContextTop({ environment: node.environment }); return defer((node) => { return tmplWithWrapper({ contextName: contextName, setupCode: setupCode, body: node.body }); }); } } }; module.exports = [ // FIXME: desugar-search-path require("./desugar-inherits"), require("./desugar-attrsets"), require("./desugar-let-attribute-set"), require("./desugar-interpolation-expressions"), require("./mangle-identifiers"), require("./let-in"), require("./literals"), require("./lists"), require("./functions"), require("./attribute-sets"), trivial, ];