joyent-portal/packages/pseudo-json-ast/src/index.js

110 lines
2.7 KiB
JavaScript
Raw Normal View History

import { parse_dammit as looseParse } from 'acorn/dist/acorn_loose';
import isUndefined from 'lodash.isundefined';
import isNull from 'lodash.isnull';
import hasOwnProp from 'has-own-prop';
export const loc = Symbol('pseudo-json-ast-loc');
const isPrimitive = v =>
Number.isNaN(v) || isNull(v) || isUndefined(v) || typeof v === 'symbol';
const isPrimitiveNode = node =>
isPrimitive(node.value) || !hasOwnProp(node, 'value');
const visitors = {
// eslint-disable-next-line new-cap
VariableDeclaration: (node = {}, ctx = {}) => walk(node.declarations, ctx),
// eslint-disable-next-line new-cap
VariableDeclarator: (node = {}, ctx = {}) => walk([node.init], ctx),
// eslint-disable-next-line new-cap
ObjectExpression: (node = {}, ctx = {}) =>
walk(node.properties, {
[loc]: node.loc
}),
// eslint-disable-next-line new-cap
Property: (node = {}, ctx = {}) => {
const value = walk([node.value]);
const isPrimitiveProperty = !node.key.value || isPrimitive(value);
if (isPrimitiveProperty) {
return ctx;
}
ctx[node.key.value] = value;
return ctx;
},
// eslint-disable-next-line new-cap
Literal: (node = {}, ctx = {}) => {
if (isPrimitiveNode(node)) {
return node.value;
}
const wrappable = Constructor => () => {
const v = new Constructor(node.value);
v[loc] = node.loc;
return v;
};
const object = () => {
node.value[loc] = node.loc;
return node.value;
};
const types = {
boolean: wrappable(Boolean),
number: wrappable(Number),
string: wrappable(String),
function: object,
object
};
return types[typeof node.value]();
},
// eslint-disable-next-line new-cap
Identifier: (node = {}) => undefined,
// eslint-disable-next-line new-cap
ArrayExpression: (node = {}) => {
const ctx = [];
ctx[loc] = node.loc;
return walk(node.elements, ctx);
}
};
const walk = (nodes = [], ctx = {}) => {
const onNode = (node, ctx, fallback) => {
const visitor = visitors[node.type];
return visitor ? visitor(node, ctx) : fallback;
};
const walkObj = () =>
nodes.reduce((sum, node) => onNode(node, sum, sum), ctx);
const walkArr = () => {
const arr = nodes.map(node => onNode(node, ctx, null)).filter(Boolean);
arr[loc] = ctx[loc];
return arr;
};
return Array.isArray(ctx) ? walkArr() : walkObj();
};
export default input => {
const isValid = typeof input === 'string' && input.trim().indexOf('{') === 0;
if (!isValid) {
return null;
}
const ast = looseParse(`var payload = ${input}`, {
ecmaVersion: 5,
sourceType: 'script',
locations: true,
range: true
});
return walk(ast.body);
};