All files / lib/pipeline/module/compiler varModule.ts

95.74% Statements 45/47
89.47% Branches 17/19
100% Functions 7/7
95.74% Lines 45/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139  26x                     26x 26x         26x   1697x   104x   1593x   18x     18x 18x   1575x   30x   1545x   5x   1540x   44x           1556x   29x 29x           26x   92x     92x   80x       26x   5x     5x   5x     5x       5x 5x     26x 5x     5x     5x     5x     4x     1x             26x   97x 74x       23x 23x                             26x   88x     88x  
import {HtmlCompilerContext, HtmlCompilerModule} from '../htmlCompiler';
import {
    DocumentNode,
    MDataNode,
    MDataNodeRef, MFragmentNode,
    MimeType,
    MScopeNode,
    MVarNode,
    Node,
    ScopeData,
    TagNode
} from '../../..';
import {resolveResPath} from '../../../fs/pathUtils';
import {convertSnakeCaseToCamelCase} from '../../../util/caseUtils';
 
/**
 * Compile module that implements <m-var> and <m-scope> parsing
 */
export class VarModule implements HtmlCompilerModule {
    async enterNode(htmlContext: HtmlCompilerContext): Promise<void> {
        if (DocumentNode.isDocumentNode(htmlContext.node)) {
            // if document, then bind root scope and we are done
            htmlContext.node.setRootScope(htmlContext.pipelineContext.fragmentContext.scope);
 
        } else if (MVarNode.isMVarNode(htmlContext.node)) {
            // process m-var
            bindAttributeDataToScope(htmlContext.node, htmlContext, true, htmlContext.node.getAttributes().entries());
 
            // delete when done
            htmlContext.node.removeSelf();
            htmlContext.setDeleted();
 
        } else if (MScopeNode.isMScopeNode(htmlContext.node)) {
            // process m-scope, but leave until later to cleanup
            bindAttributeDataToScope(htmlContext.node, htmlContext, false, htmlContext.node.getAttributes().entries());
 
        } else if (MDataNode.isMDataNode(htmlContext.node)) {
            // process m-data
            await processMData(htmlContext.node, htmlContext);
 
        } else if (MFragmentNode.isMFragmentNode(htmlContext.node)) {
            // process m-fragment
            bindAttributeDataToScope(htmlContext.node, htmlContext, false, htmlContext.node.parameters);
        }
    }
 
    exitNode(htmlContext: HtmlCompilerContext): void {
        // m-scope removal is delayed until now to preserve the scope data
        if (MScopeNode.isMScopeNode(htmlContext.node)) {
            // delete node, but leave children in place
            htmlContext.node.removeSelf(true);
            htmlContext.setDeleted();
        }
    }
}
 
 
export function bindAttributeDataToScope(node: TagNode, htmlContext: HtmlCompilerContext, useParentScope: boolean, attributes: Iterable<[string, unknown]>): void {
    // m-var writes into its parent's scope instead of using its own
    const targetScope = getTargetScope(node, htmlContext, useParentScope);
 
    // promote variables to scope
    for (const variable of attributes) {
        // copy value to scope
        saveCompiledAttributeToScope(targetScope, variable[0], variable[1]);
    }
}
 
export async function processMData(node: MDataNode, htmlContext: HtmlCompilerContext): Promise<void> {
    // get target scope - this writes into the containing scope rather than its own
    const targetScope = getTargetScope(node, htmlContext, true);
 
    // process each reference
    for (const reference of node.references) {
        // compile content
        const compiledContent: unknown = await compileReference(reference, node, htmlContext);
 
        // bind to scope
        saveCompiledAttributeToScope(targetScope, reference.varName, compiledContent);
    }
 
    // delete node
    node.removeSelf();
    htmlContext.setDeleted();
}
 
export async function compileReference(reference: MDataNodeRef, node: MDataNode, htmlContext: HtmlCompilerContext): Promise<unknown> {
    const pipelineContext = htmlContext.pipelineContext;
 
    // compute path to reference
    const resPath = resolveResPath(reference.resPath, pipelineContext.fragment.path);
 
    // get value
    const rawValue = await pipelineContext.pipeline.getRawText(resPath, node.type);
 
    // parse as correct type
    switch (node.type) {
        case MimeType.JSON:
            // JSON data
            return JSON.parse(rawValue) as unknown;
        case MimeType.TEXT:
            // text data
            return String(rawValue);
        default:
            // pass unknown types as-is
            return rawValue;
    }
}
 
export function getTargetScope(node: Node, htmlContext: HtmlCompilerContext, useParentScope: boolean): ScopeData {
    // if not using the parent scope, then we can take current node's data and use that as scope
    if (!useParentScope) {
        return node.nodeData;
    }
 
    // if we are using the parent scope and there is a parent node, then use that scope
    Eif (node.parentNode != null) {
        return node.parentNode.nodeData;
    }
 
    // if we are using the parent scope but there is no parent node, then fall back to root scope
    return htmlContext.pipelineContext.fragmentContext.scope;
}
 
/**
 * Binds a compiled data attribute to a scope object.
 * Performs case-conversion on the attribute name
 *
 * @param scope Scope object to save to
 * @param attributeName Attribute name
 * @param compiledValue Value of the attribute
 */
export function saveCompiledAttributeToScope(scope: ScopeData, attributeName: string, compiledValue: unknown): void {
    // convert snake-case attribute name to camelCase scope name
    const scopeName: string = convertSnakeCaseToCamelCase(attributeName);
 
    // save to scope
    scope[scopeName] = compiledValue;
}