"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 tmplConditionalExpression = templateExpression(` (() => { if (%%condition%%) { return %%consequence%%; } else { return %%alternative%%; } })() `); 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; let safeName = 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 }); } }, NixUnaryOperation: (_node, { defer }) => { return defer((node) => { if (node.operator === "-" || node.operator === "!") { return types.unaryExpression(node.operator, node.argument, true); } else { throw new Error(`Unsupported operator: ${node.operator}`); } }); }, 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 if (node.operator === "&&" || node.operator === "||") { return types.logicalExpression(node.operator, node.left, node.right); } if (node.operator === "//") { return types.objectExpression([ types.spreadElement(node.left), types.spreadElement(node.right) ]); } else if (node.operator === "++") { return types.arrayExpression([ types.spreadElement(node.left), types.spreadElement(node.right) ]); } else { return types.binaryExpression(node.operator, node.left, node.right); } }); }, NixWithExpression: (node, { defer, setContext, getContextOptional }) => { // TODO: Can we optimize for the fast case (no nested `with`) by referencing the source attrset directly? let parentContext = getContextOptional("implicitContext"); let hasParent = (parentContext != null); let contextName = `$implicit${implicitContextCounter++}`; setContext(null, "implicitContext", contextName); return defer((node) => { let setupCode = (hasParent) ? tmplImplicitContextNested({ environment: node.environment, parent: parentContext }) : tmplImplicitContextTop({ environment: node.environment }); return tmplWithWrapper({ contextName: contextName, setupCode: setupCode, body: node.body }); }); }, NixConditional: (_node, { defer }) => { // condition, consequence, alternative return defer((node) => { return tmplConditionalExpression({ condition: node.condition, consequence: node.consequence, alternative: node.alternative }); }); } } }; 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, ];