|
1 | | -const {parse} = require('@babel/parser') |
| 1 | +import {parse} from '@babel/parser' |
| 2 | +import {Node, NodePath} from '@babel/traverse' |
| 3 | +import {Expression, Statement, VariableDeclaration} from '@babel/types' |
2 | 4 | // const printAST = require('ast-pretty-print') |
3 | | -const {createMacro} = require('../../') |
| 5 | +import {createMacro} from '../../' |
4 | 6 |
|
5 | | -module.exports = createMacro(evalMacro) |
6 | | - |
7 | | -function evalMacro({references, state}) { |
| 7 | +export default createMacro(function evalMacro({references, state}) { |
8 | 8 | references.default.forEach(referencePath => { |
9 | | - if (referencePath.parentPath.type === 'TaggedTemplateExpression') { |
10 | | - asTag(referencePath.parentPath.get('quasi'), state) |
11 | | - } else if (referencePath.parentPath.type === 'CallExpression') { |
12 | | - asFunction(referencePath.parentPath.get('arguments'), state) |
13 | | - } else if (referencePath.parentPath.type === 'JSXOpeningElement') { |
14 | | - asJSX( |
15 | | - { |
16 | | - attributes: referencePath.parentPath.get('attributes'), |
17 | | - children: referencePath.parentPath.parentPath.get('children'), |
18 | | - }, |
19 | | - state, |
20 | | - ) |
| 9 | + if (referencePath.parentPath?.type === 'TaggedTemplateExpression') { |
| 10 | + asTag(referencePath.parentPath?.get('quasi')) |
| 11 | + } else if (referencePath.parentPath?.type === 'CallExpression') { |
| 12 | + const args = referencePath.parentPath?.get('arguments') |
| 13 | + if (!Array.isArray(args)) { |
| 14 | + throw new Error('Was expecting array') |
| 15 | + } |
| 16 | + asFunction(args) |
| 17 | + } else if (referencePath.parentPath?.type === 'JSXOpeningElement') { |
| 18 | + asJSX({ |
| 19 | + attributes: referencePath.parentPath?.get('attributes'), |
| 20 | + children: referencePath.parentPath?.parentPath?.get('children'), |
| 21 | + }) |
21 | 22 | } else { |
22 | 23 | // TODO: throw a helpful error message |
23 | 24 | } |
24 | 25 | }) |
25 | | -} |
| 26 | +}) |
| 27 | + |
| 28 | +function asTag(quasiPath: NodePath | NodePath[]) { |
| 29 | + if (Array.isArray(quasiPath)) { |
| 30 | + throw new Error("Don't know how to handle arrays") |
| 31 | + } |
| 32 | + |
| 33 | + const parentQuasi = quasiPath.parentPath?.get('quasi') |
26 | 34 |
|
27 | | -function asTag(quasiPath) { |
28 | | - const value = quasiPath.parentPath.get('quasi').evaluate().value |
29 | | - quasiPath.parentPath.replaceWith(evalToAST(value)) |
| 35 | + if (!parentQuasi) { |
| 36 | + throw new Error('No quasi path on parent') |
| 37 | + } |
| 38 | + |
| 39 | + if (Array.isArray(parentQuasi)) { |
| 40 | + throw new Error("Don't know how to handle arrays") |
| 41 | + } |
| 42 | + const value = parentQuasi.evaluate().value |
| 43 | + quasiPath.parentPath?.replaceWith(evalToAST(value)) |
30 | 44 | } |
31 | 45 |
|
32 | | -function asFunction(argumentsPaths) { |
| 46 | +function asFunction(argumentsPaths: NodePath[]) { |
33 | 47 | const value = argumentsPaths[0].evaluate().value |
34 | | - argumentsPaths[0].parentPath.replaceWith(evalToAST(value)) |
| 48 | + argumentsPaths[0].parentPath?.replaceWith(evalToAST(value)) |
| 49 | +} |
| 50 | + |
| 51 | +type NodeWithValue = Node & { |
| 52 | + value: any |
| 53 | +} |
| 54 | + |
| 55 | +function isNodeWithValue(node: Node): node is NodeWithValue { |
| 56 | + return Object.prototype.hasOwnProperty.call(node, 'value') |
35 | 57 | } |
36 | 58 |
|
37 | 59 | // eslint-disable-next-line no-unused-vars |
38 | | -function asJSX({attributes, children}) { |
| 60 | +function asJSX({ |
| 61 | + attributes, |
| 62 | + children, |
| 63 | +}: { |
| 64 | + attributes: NodePath | NodePath[] |
| 65 | + children: NodePath | NodePath[] | undefined |
| 66 | +}) { |
39 | 67 | // It's a shame you cannot use evaluate() with JSX |
40 | | - const value = children[0].node.value |
41 | | - children[0].parentPath.replaceWith(evalToAST(value)) |
| 68 | + if (!Array.isArray(children)) { |
| 69 | + throw new Error("Don't know how to handle single children") |
| 70 | + } |
| 71 | + const firstChild = children[0] |
| 72 | + if (!isNodeWithValue(firstChild.node)) { |
| 73 | + throw new Error("Don't know to handle nodes without values") |
| 74 | + } |
| 75 | + const value = firstChild.node.value |
| 76 | + firstChild.parentPath?.replaceWith(evalToAST(value)) |
42 | 77 | } |
43 | 78 |
|
44 | | -function evalToAST(value) { |
45 | | - let x |
| 79 | +function evalToAST(value: Expression | null | undefined): Expression { |
| 80 | + let x: Record<string, unknown> = {} |
46 | 81 | // eslint-disable-next-line |
47 | 82 | eval(`x = ${value}`) |
48 | 83 | return thingToAST(x) |
49 | 84 | } |
50 | 85 |
|
51 | | -function thingToAST(object) { |
| 86 | +function isVariableDeclaration( |
| 87 | + statement: Statement, |
| 88 | +): statement is VariableDeclaration { |
| 89 | + return statement.type === 'VariableDeclaration' |
| 90 | +} |
| 91 | + |
| 92 | +function thingToAST(object: Record<string, unknown>) { |
52 | 93 | const fileNode = parse(`var x = ${JSON.stringify(object)}`) |
53 | | - return fileNode.program.body[0].declarations[0].init |
| 94 | + const firstStatement = fileNode.program.body[0] |
| 95 | + |
| 96 | + if (!isVariableDeclaration(firstStatement)) { |
| 97 | + throw new Error('Only know how to handle VariableDeclarations') |
| 98 | + } |
| 99 | + |
| 100 | + const initDeclaration = firstStatement.declarations[0].init |
| 101 | + |
| 102 | + if (!initDeclaration) { |
| 103 | + throw new Error('Was expecting expression') |
| 104 | + } |
| 105 | + return initDeclaration |
54 | 106 | } |
0 commit comments