Add complete test coverage and JSdoc headers.

master
Sir Robert Burbridge 2023-11-29 11:47:19 -05:00
parent ce403c65e6
commit e2302abad2
6 changed files with 136 additions and 46 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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 = [
{

View File

@ -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()", () => {