UNPKG

@magic/core

Version:

@magic core. generate static pages and serverless lambdas. ~5kb client boilerplate.

322 lines (300 loc) 14 kB
import is from '@magic/types' import log from '@magic/log' import { handleLink } from '../handleLink.mjs' // import { replaceSlashSlash } from '../replaceSlashSlash.mjs' const validKeys = ['src', 'srcset', 'logo', 'href', 'to'] const noopTypes = [ 'BooleanLiteral', 'NullLiteral', 'NumericLiteral', 'RegExpLiteral', 'ThisExpression', 'EmptyStatement', 'Identifier', ] export const used = { modules: new Set(), } export const visit = ({ app, config, parent, par }) => { if (!parent) { return parent } if (Array.isArray(parent)) { return parent.map(n => visit({ par: parent, parent: n, app, config })) } if (parent.type === 'VariableDeclaration') { parent.declarations = parent.declarations.map(decl => visit({ par: parent, parent: decl, app, config }), ) } else if (parent.type === 'ExpressionStatement') { parent.expression = visit({ par: parent, parent: parent.expression, app, config }) } else if (parent.type === 'AssignmentExpression') { parent.right = visit({ par: parent, parent: parent.right, app, config }) parent.left = visit({ par: parent, parent: parent.left, app, config }) } else if (parent.type === 'ArrowFunctionExpression') { if (parent.params) { parent.params = visit({ par: parent, parent: parent.params, app, config }) parent.body = visit({ par: parent, parent: parent.body, app, config }) } else if (parent.body.callee) { parent.body.callee = visit({ par: parent, parent: parent.body.callee, app, config }) parent.params = visit({ par: parent, parent: parent.params, app, config }) } else if (parent.body.stmts) { parent.body.stmts = parent.body.stmts.map(stmt => visit({ par: parent, parent: stmt, app, config }), ) } else { log.warn('unhandled ArrowFunctionExpression', parent) } } else if (parent.type === 'IfStatement') { parent.test = visit({ par: parent, parent: parent.test, app, config }) parent.consequent = visit({ par: parent, parent: parent.consequent, app, config }) if (parent.alternate) { parent.alternate = visit({ par: parent, parent: parent.alternate, app, config }) } } else if (parent.type === 'CallExpression') { const isModule = Object.keys(app.modules).includes(parent.callee?.value) const lib = Object.keys(app.lib).find(v => v === parent.callee?.value) const isLib = lib && is.function(lib) const name = parent.callee.value if (isModule) { // console.log('is module', name) used.modules.add(name) // } else { // console.log('not a module', name) } if (isLib) { // console.log('is lib', name) } /* remove CHECK_PROPS function calls */ if (parent.callee.value === 'CHECK_PROPS') { parent.type = 'Invalid' } else { if (parent.callee.value === 'source' || parent.callee.value === 'img') { const ignoredProps = ['Identifier', 'TemplateLiteral', 'BinaryExpression'] parent.arguments.forEach(arg => { arg.expression?.properties?.forEach(prop => { if (prop.type === 'KeyValueProperty') { if (validKeys.includes(prop.key?.value)) { let url if (prop.value.type === 'StringLiteral') { url = prop.value.value } else if (prop.value.type === 'TemplateLiteral') { /* * We return on TemplateLiterals * there are variable in play here, * we do not want to risk mutating them */ return } else if (ignoredProps.includes(prop.value.type)) { /* * we return on Identifier, TemplateLiteral and BinaryExpression * they mean this is a variable or function being passed, * that we do not want to mutate */ return } else { log.warn('W_UNKNOWN_URL_TYPE', 'could not find valid ast url type for', prop) url = config.WEB_ROOT } const isInternal = !url.includes('://') const isRooted = url.startsWith(config.WEB_ROOT) if (isInternal && !isRooted) { if (!url) { url = config.WEB_ROOT } else { url = handleLink({ app, href: url.substr(0, url.length - 1), WEB_ROOT: config.WEB_ROOT, }) } if (prop.value.type === 'StringLiteral') { prop.value.value = url prop.value.raw = `'${url}'` } else if (prop.value?.quasis) { prop.value.quasis[0].cooked = url prop.value.quasis[0].raw = `'${url}'` } } } } }) }) } parent.arguments = parent.arguments.map(arg => { arg.expression = visit({ par: parent, parent: arg.expression, app, config }) if (arg.expression.type === 'StringLiteral') { if (Object.keys(app.modules).includes(parent.callee.value)) { const expr = { type: 'ExpressionStatement', span: { start: 0, end: 22, ctxt: 0, }, expression: { type: 'CallExpression', span: { start: 0, end: 22, ctxt: 0, }, callee: { type: 'Identifier', span: { start: 0, end: 4, ctxt: 0, }, value: 'text', optional: false, }, arguments: [ { spread: null, expression: { type: 'StringLiteral', span: { start: 5, end: 21, ctxt: 0, }, value: arg.expression.value, raw: arg.expression.raw, }, }, ], typeArguments: null, }, } // arg = expr } } return arg }) } } else if (parent.type === 'VariableDeclarator') { parent.id = visit({ par: parent, parent: parent.id, app, config }) parent.init = visit({ par: parent, parent: parent.init, app, config }) } else if (parent.type === 'ObjectExpression') { parent.properties = parent.properties.map(prop => visit({ par: parent, parent: prop, app, config }), ) } else if (parent.type === 'KeyValueProperty') { if (parent.value.type === 'StringLiteral') { if (validKeys.includes(parent.key.value)) { parent.value.value = handleLink({ app, href: parent.value.value, WEB_ROOT: config.WEB_ROOT, }) } } parent.key = visit({ par: parent, parent: parent.key, app, config }) parent.value = visit({ par: parent, parent: parent.value, app, config }) } else if (parent.type === 'MemberExpression') { parent.property = visit({ par: parent, parent: parent.property, app, config }) parent.object = visit({ par: parent, parent: parent.object, app, config }) } else if (parent.type === 'ArrayExpression') { parent.elements = parent.elements.map(ele => { ele.expression = visit({ par: parent, parent: ele.expression, undefined, app, config }) return ele }) } else if (parent.type === 'ArrayPattern') { parent.elements = parent.elements.map(ele => visit({ par: parent, parent: ele, app, config })) } else if (parent.type === 'TemplateLiteral') { parent.quasis = parent.quasis.map(quasi => { quasi.raw = visit({ par: parent, parent: quasi.raw, app, config }) quasi.cooked = visit({ par: parent, parent: quasi.cooked, app, config }) return quasi }) } else if (parent.type === 'SpreadElement') { parent.arguments = visit({ par: parent, parent: parent.arguments, app, config }) } else if (parent.type === 'BlockStatement') { parent.stmts = parent.stmts.map(stmt => visit({ par: parent, parent: stmt, app, config })) } else if (parent.type === 'ReturnStatement') { parent.argument = visit({ par: parent, parent: parent.argument, app, config }) } else if (parent.type === 'ObjectPattern') { parent.properties.map(prop => visit({ par: parent, parent: prop, app, config })) } else if (parent.type === 'AssignmentPattern') { parent.left = visit({ par: parent, parent: parent.left, app, config }) parent.right = visit({ par: parent, parent: parent.right, app, config }) } else if (parent.type === 'AssignmentPatternProperty') { parent.key = visit({ par: parent, parent: parent.key, app, config }) } else if (parent.type === 'ConditionalExpression') { parent.test = visit({ par: parent, parent: parent.test, app, config }) parent.consequent = visit({ par: parent, parent: parent.consequent, app, config }) parent.alternate = visit({ par: parent, parent: parent.alternate, app, config }) } else if (parent.type === 'BinaryExpression') { parent.left = visit({ par: parent, parent: parent.left, app, config }) parent.right = visit({ par: parent, parent: parent.right, app, config }) } else if (parent.type === 'UnaryExpression') { parent.argument = visit({ par: parent, parent: parent.argument, app, config }) } else if (parent.type === 'NewExpression') { parent.callee = visit({ par: parent, parent: parent.callee, app, config }) } else if (parent.type === 'ParenthesisExpression') { parent.expression = visit({ par: parent, parent: parent.expression, app, config }) } else if (parent.type === 'RestElement') { parent.argument = visit({ par: parent, parent: parent.argument, undefined, app, config }) } else if (parent.type === 'KeyValuePatternProperty') { parent.key = visit({ par: parent, parent: parent.key, app, config }) parent.value = visit({ par: parent, parent: parent.value, app, config }) } else if (parent.type === 'TryStatement') { parent.block = visit({ par: parent, parent: parent.block, app, config }) parent.handler = visit({ par: parent, parent: parent.handler, app, config }) } else if (parent.type === 'CatchClause') { parent.param = visit({ par: parent, parent: parent.param, app, config }) parent.body = visit({ par: parent, parent: parent.body, app, config }) } else if (parent.type === 'Computed') { parent.expression = visit({ par: parent, parent: parent.expression, app, config }) } else if (parent.type === 'OptionalChainingExpression') { parent.expr = visit({ par: parent, parent: parent.expr, app, config }) } else if (parent.type === 'ForStatement') { parent.init = visit({ par: parent, parent: parent.init, app, config }) parent.test = visit({ par: parent, parent: parent.test, app, config }) parent.update = visit({ par: parent, parent: parent.update, app, config }) parent.body = visit({ par: parent, parent: parent.body, app, config }) } else if (parent.type === 'FunctionExpression') { parent.params = visit({ par: parent, parent: parent.params, app, config }) parent.body = visit({ par: parent, parent: parent.body, app, config }) parent.decorators = visit({ par: parent, parent: parent.decorators, app, config }) } else if (parent.type === 'Parameter') { parent.pat = visit({ par: parent, parent: parent.pat, app, config }) } else if (parent.type === 'ForInStatement') { parent.left = visit({ par: parent, parent: parent.left, app, config }) parent.right = visit({ par: parent, parent: parent.right, app, config }) parent.body = visit({ par: parent, parent: parent.body, app, config }) } else if (parent.type === 'UpdateExpression') { parent.argument = visit({ par: parent, parent: parent.argument, app, config }) } else if (parent.type === 'WhileStatement') { parent.test = visit({ par: parent, parent: parent.test, app, config }) parent.body = visit({ par: parent, parent: parent.body, app, config }) } else if (parent.type === 'BreakStatement') { parent.label = visit({ par: parent, parent: parent.label, app, config }) } else if (parent.type === 'SequenceExpression') { parent.expressions = visit({ par: parent, parent: parent.expressions, app, config }) } else if (parent.type === 'ContinueStatement') { parent.label = visit({ par: parent, parent: parent.label, app, config }) } else if (parent.type === 'ImportDeclaration') { parent.specifiers = visit({ par: parent, parent: parent.specifiers, app, config }) } else if (parent.type === 'ImportSpecifier') { parent.local = visit({ par: parent, parent: parent.local, app, config }) } else if (parent.type === 'ExportNamedDeclaration') { parent.specifiers = visit({ par: parent, parent: parent.specifiers, app, config }) } else if (parent.type === 'ExportSpecifier') { parent.orig = visit({ par: parent, parent: parent.orig, app, config }) } else if (parent.type === 'StringLiteral') { // console.log(parent, par) } else if (parent.type === 'AwaitExpression') { parent.argument = visit({ par: parent, parent: parent.argument, app, config }) // noop } else if (parent.type === 'ForOfStatement') { parent.body = visit({ par: parent, parent: parent.body, app, config }) parent.left = visit({ par: parent, parent: parent.left, app, config }) parent.right = visit({ par: parent, parent: parent.right, app, config }) } else if (noopTypes.includes(parent.type)) { } else if (parent.type) { log.warn('unexpected parent type', parent.type, parent) } return parent }