Add complete test coverage and JSdoc headers.
parent
ce403c65e6
commit
e2302abad2
|
@ -1,14 +1,33 @@
|
|||
/**
|
||||
* @module @thefarce/loom/interpolate
|
||||
* Provides functionality for interpolating nodes and values within trees based
|
||||
* on context.
|
||||
*/
|
||||
|
||||
import { visitParents } from 'unist-util-visit-parents';
|
||||
import { createContext, runInContext } from 'vm';
|
||||
import { cloneNode } from './util.mjs';
|
||||
import { is } from 'unist-util-is';
|
||||
|
||||
export function getUnifiedContext (lineage = []) {
|
||||
/**
|
||||
* Accumulates context from a lineage of nodes.
|
||||
*
|
||||
* @param {Array} lineage - The lineage of nodes.
|
||||
* @returns {Object} The accumulated context.
|
||||
*/
|
||||
export function getUnifiedContext(lineage = []) {
|
||||
return lineage.reduce((context, node) => {
|
||||
return Object.assign({}, context, node?.data?.context || {});
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a tree structure by applying context masks.
|
||||
*
|
||||
* @param {Object} tree - The tree to be interpolated.
|
||||
* @param {Object} contextMask - Contextual data used for interpolation.
|
||||
* @returns {Object} The interpolated tree.
|
||||
*/
|
||||
export function interpolateTree(tree, contextMask = {}) {
|
||||
let result = cloneNode(tree);
|
||||
|
||||
|
@ -21,20 +40,23 @@ export function interpolateTree(tree, contextMask = {}) {
|
|||
|
||||
let interpolatedNode = interpolateNode(node, context);
|
||||
Object.assign(node, interpolatedNode);
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a single node by applying context masks.
|
||||
*
|
||||
* @param {Object} node - The node to interpolate.
|
||||
* @param {Object} contextMask - Contextual data for interpolation.
|
||||
* @returns {Object} The interpolated node.
|
||||
*/
|
||||
export function interpolateNode(node, contextMask = {}) {
|
||||
const result = cloneNode(node);
|
||||
|
||||
let _context = Object.assign({}, node?.data?.context || {}, contextMask);
|
||||
|
||||
// Loop through the node object's properties. Interpolate any values that
|
||||
// are strings _unless_ it is the 'type', 'data', or 'position' property.
|
||||
// Just skip those.
|
||||
for (const key in node) {
|
||||
if (['type', 'data', 'position', 'children'].includes(key)) {
|
||||
continue;
|
||||
|
@ -46,7 +68,14 @@ export function interpolateNode(node, contextMask = {}) {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function interpolateValue (value, context = {}) {
|
||||
/**
|
||||
* Interpolates different types of values based on the context.
|
||||
*
|
||||
* @param {any} value - The value to interpolate.
|
||||
* @param {Object} context - The context for interpolation.
|
||||
* @returns {any} The interpolated value.
|
||||
*/
|
||||
export function interpolateValue(value, context = {}) {
|
||||
if (typeof value === 'string') {
|
||||
return interpolateString(value, context);
|
||||
}
|
||||
|
@ -61,6 +90,13 @@ export function interpolateValue (value, context = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a string by executing it in a given context.
|
||||
*
|
||||
* @param {string} str - The string to interpolate.
|
||||
* @param {Object} data - The context for interpolation.
|
||||
* @returns {string} The interpolated string.
|
||||
*/
|
||||
export function interpolateString(str, data) {
|
||||
try {
|
||||
const context = createContext({ ...data, ...global });
|
||||
|
@ -70,6 +106,13 @@ export function interpolateString(str, data) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates each element in an array based on the context.
|
||||
*
|
||||
* @param {Array} arr - The array to interpolate.
|
||||
* @param {Object} context - The context for interpolation.
|
||||
* @returns {Array} The interpolated array.
|
||||
*/
|
||||
export function interpolateArray(arr, context = {}) {
|
||||
let result = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
|
@ -78,6 +121,13 @@ export function interpolateArray(arr, context = {}) {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates each property of an object based on the context.
|
||||
*
|
||||
* @param {Object} obj - The object to interpolate.
|
||||
* @param {Object} context - The context for interpolation.
|
||||
* @returns {Object} The interpolated object.
|
||||
*/
|
||||
export function interpolateObject(obj, context = {}) {
|
||||
for (const key in obj) {
|
||||
obj[key] = interpolateValue(obj[key], context);
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { visitParents } from 'unist-util-visit-parents';
|
||||
|
||||
export function replaceNode (tree, test, transformer) {
|
||||
visitParents(tree, test, (node, ancestors) => {
|
||||
const parent = ancestors[ancestors.length - 1]
|
||||
|
||||
if (!parent?.children?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let position = -1;
|
||||
parent.children.forEach((child, index) => {
|
||||
if (child === node) {
|
||||
position = index;
|
||||
}
|
||||
})
|
||||
|
||||
if (position) {
|
||||
parent.children.splice(position, 1, transformer(node));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default replaceNode;
|
|
@ -1,7 +1,21 @@
|
|||
/**
|
||||
* @module @thefarce/loom/transform
|
||||
* Provides functionality to apply transformations to Abstract Syntax Trees
|
||||
* (ASTs).
|
||||
*/
|
||||
|
||||
import { visitParents } from 'unist-util-visit-parents';
|
||||
import { isAstNode } from './util.mjs';
|
||||
import { is } from 'unist-util-is';
|
||||
|
||||
/**
|
||||
* Transforms an AST using a series of transformer functions.
|
||||
*
|
||||
* @param {Object} ast - The AST to be transformed.
|
||||
* @param {...Function} transformers - Functions that define how each node
|
||||
* in the AST should be transformed.
|
||||
* @returns {Object} The transformed AST.
|
||||
*/
|
||||
export function transform (ast, ...transformers) {
|
||||
let tree = JSON.parse(JSON.stringify(ast));
|
||||
|
||||
|
@ -81,4 +95,3 @@ export function transform (ast, ...transformers) {
|
|||
}
|
||||
|
||||
export default transform;
|
||||
|
||||
|
|
43
src/util.mjs
43
src/util.mjs
|
@ -1,26 +1,45 @@
|
|||
export function cloneNode (node) {
|
||||
/**
|
||||
* @module util
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clones a node by creating a deep copy.
|
||||
*
|
||||
* @param {Object} node - The node to be cloned.
|
||||
* @returns {Object} A deep clone of the original node.
|
||||
*/
|
||||
export function cloneNode(node) {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
}
|
||||
|
||||
export function createNode ({
|
||||
/**
|
||||
* Creates a new node object with specified type and context.
|
||||
*
|
||||
* @param {Object} [options={}] - Configuration options for the node.
|
||||
* @param {string} [options.type='created-node'] - The type of the node.
|
||||
* @param {Object} [options.context={}] - The context of the node.
|
||||
* @returns {Object} A new node object with the specified type and context.
|
||||
*/
|
||||
export function createNode({
|
||||
type = 'created-node',
|
||||
context = {},
|
||||
} = {}) {
|
||||
const ast = {
|
||||
return {
|
||||
type,
|
||||
|
||||
data: {
|
||||
context,
|
||||
},
|
||||
};
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
// Test if the node is an object with a "type" value (including undefined)
|
||||
export function isAstNode (node) {
|
||||
return (typeof node === 'object')
|
||||
&& (node !== null)
|
||||
&& ('type' in node)
|
||||
;
|
||||
/**
|
||||
* Tests if the given value is an AST (Abstract Syntax Tree) node.
|
||||
* An AST node is defined as an object with a "type" property.
|
||||
*
|
||||
* @param {any} node - The value to test.
|
||||
* @returns {boolean} True if the value is an object with a "type" property,
|
||||
* false otherwise.
|
||||
*/
|
||||
export function isAstNode(node) {
|
||||
return (typeof node === 'object') && (node !== null) && ('type' in node);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
getUnifiedContext,
|
||||
interpolateArray,
|
||||
interpolateNode,
|
||||
interpolateObject,
|
||||
interpolateString,
|
||||
interpolateTree,
|
||||
interpolateValue,
|
||||
getUnifiedContext,
|
||||
} from '../src/interpolate.mjs'
|
||||
|
||||
import {
|
||||
|
@ -320,7 +322,24 @@ describe("interpolateString()", () => {
|
|||
});
|
||||
})
|
||||
|
||||
describe("interpolateArray()", () => {
|
||||
it("should interpolate arrays without a context", () => {
|
||||
expect(interpolateArray([1,2,3])).toEqual([1,2,3]);
|
||||
})
|
||||
});
|
||||
|
||||
describe("interpolateObject()", () => {
|
||||
it("should interpolate objects without a context", () => {
|
||||
expect(interpolateObject({a:1,b:2,c:3})).toEqual({a:1,b:2,c:3});
|
||||
})
|
||||
});
|
||||
|
||||
describe("interpolateValue()", () => {
|
||||
it("should pass through without a context", () => {
|
||||
const str = "sum: ${count + 5}";
|
||||
expect(interpolateValue(str)).toEqual("sum: ${count + 5}");
|
||||
});
|
||||
|
||||
it("should interpolate strings", () => {
|
||||
const str = "sum: ${count + 5}";
|
||||
const context = { count: 9 };
|
||||
|
@ -332,7 +351,6 @@ describe("interpolateValue()", () => {
|
|||
const context = { count: 9 };
|
||||
expect(interpolateValue(arr, context)).toEqual(["9"]);
|
||||
|
||||
expect(interpolateValue([1,2,3])).toEqual([1,2,3]);
|
||||
});
|
||||
|
||||
it("should interpolate objects", () => {
|
||||
|
@ -351,6 +369,11 @@ describe("interpolateValue()", () => {
|
|||
});
|
||||
|
||||
describe("getUnifiedContext()", () => {
|
||||
|
||||
it("should provide a context object without a lineage", () => {
|
||||
expect(getUnifiedContext()).toEqual({});
|
||||
});
|
||||
|
||||
it("should return the unified context", () => {
|
||||
const ancestry = [
|
||||
{
|
||||
|
|
|
@ -55,6 +55,15 @@ describe("createNode()", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should create a generic AST node with no arguments", () => {
|
||||
expect(createNode({type:"pig"})).toEqual({
|
||||
type: 'pig',
|
||||
data: {
|
||||
context: {},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe("isAstNode()", () => {
|
||||
|
|
Loading…
Reference in New Issue