All files / lib html.js

100% Statements 153/153
100% Branches 26/26
100% Functions 3/3
100% Lines 153/153

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 140 141 142 143 144 145 146 147 148 149 150 151 152 1537x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 105x 105x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 215x 215x 215x 215x 6x 6x 215x 215x 215x 215x 3x 3x 215x 215x 215x 116x 5x 5x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 111x 116x 116x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 198x 198x 198x 198x 198x 105x 105x 105x 105x 105x 105x 105x 105x 105x 11x 11x 11x 105x 105x 26x 26x 26x 105x 105x 49x 49x 49x 105x 105x 105x 18x 18x 105x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 93x 215x 215x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 14x 14x 14x 14x 7x 7x
import createTree from './tree/create';
import parse, { TOKEN } from './util/parse';
import escape from './util/escape';
import decodeEntities from './util/decode-entities';
import { $$strict } from './util/symbols';
import { EMPTY, VTree, Supplemental } from './util/types';
import getConfig from './util/config';
 
const { isArray } = Array;
const isTagEx = /(<|\/)/;
 
// Get the next value from the list. If the next value is a string, make sure
// it is escaped.
const nextValue = (/** @type {any[]} */ values) => {
  const value = values.shift();
  return typeof value === 'string' ? escape(decodeEntities(value)) : value;
};
 
/**
 * Processes a tagged template, or process a string and interpolate
 * associated values. These values can be of any type and can be
 * put in various parts of the markup, such as tag names, attributes,
 * and node values.
 *
 * @param {string | string[] | TemplateStringsArray} strings
 * @param  {...any} values - test
 * @return {VTree} VTree object or null if no input strings
 */
export default function handleTaggedTemplate(strings, ...values) {
  const empty = createTree('#text', EMPTY.STR);
 
  // Do not attempt to parse empty strings.
  if (!strings) {
    return empty;
  }
 
  // If this function is used outside of a tagged template, ensure that flat
  // strings are coerced to arrays, simulating a tagged template call.
  else if (typeof strings === 'string') {
    strings = [strings];
  }
 
  // Parse only the text, no dynamic bits.
  if (strings.length === 1 && !values.length) {
    if (!strings[0]) {
      return empty;
    }
 
    const strict = /** @type {boolean} */(
      getConfig(
        'strict',
        false,
        'boolean',
        {
          strict: /** @type {any} */(handleTaggedTemplate)[$$strict]
        },
      )
    );
 
    delete /** @type {any} */(handleTaggedTemplate)[$$strict];
 
    const { childNodes } = parse(strings[0], undefined, {
      parser: { strict },
    });
 
    return createTree(childNodes.length === 1 ? childNodes[0] : childNodes);
  }
 
  // Used to store markup and tokens.
  let HTML = EMPTY.STR;
 
  // We filter the supplemental values by where they are used. Values are
  // either, children, or tags (for components).
  /** @type {Supplemental} */
  const supplemental = ({
    attributes: {},
    children: {},
    tags: {},
   });
 
  // Loop over the static strings, each break correlates to an interpolated
  // value. As these values can be dynamic, we cannot pass them to the HTML
  // parser inline (it only accepts strings). These dynamic values are indexed
  // in an object called supplemental and keyed by a incremental string token.
  // The following loop instruments the markup with these tokens that the
  // parser then uses to assemble the correct tree.
  strings.forEach((string, i) => {
    // Always add the string, we need it to parse the markup later.
    HTML += string;
 
    // If there are values, figure out where in the markup they were injected.
    if (values.length) {
      const value = nextValue(values);
      const lastCharacter = HTML.trim().slice(-1);
      const isAttribute = HTML.lastIndexOf('>') < HTML.lastIndexOf('<');
      const isTag = Boolean(lastCharacter.match(isTagEx));
      const isObject = typeof value === 'object';
      const token = `${TOKEN}${i}__`;
 
      // Injected as a tag.
      if (isTag) {
        supplemental.tags[i] = value;
        HTML += token;
      }
      // Injected as attribute.
      else if (isAttribute) {
        supplemental.attributes[i] = value;
        HTML += token;
      }
      // Injected as a child node.
      else if (isArray(value) || isObject) {
        supplemental.children[i] = createTree(value);
        HTML += token;
      }
      // Injected as something else in the markup or undefined, ignore
      // obviously falsy values used with boolean operators.
      else if (value) {
        HTML += value;
      }
    }
  });
 
  // Determine if we are in strict mode and immediately reset for the next
  // call.
  const strict = /** @type {boolean} */(getConfig('strict', false, 'boolean', {
    strict: /** @type {any} */ (handleTaggedTemplate)[$$strict],
  }));
 
  // Parse the instrumented markup to get the Virtual Tree.
  const { childNodes } = parse(HTML, supplemental, { parser: { strict } });
 
  // This makes it easier to work with a single element as a root, opposed to
  // always returning an array.
  return createTree(childNodes.length === 1 ? childNodes[0] : childNodes);
}
 
// Default to loose-mode.
delete /** @type {any} */(handleTaggedTemplate)[$$strict];
 
/**
 * Use a strict mode similar to XHTML/JSX where tags must be properly closed
 * and malformed markup is treated as an error. The default is to silently fail
 * just like HTML.
 *
 * @param {string | string[] | TemplateStringsArray} markup
 * @param  {any[]} args
 */
function setStrictMode(markup, ...args) {
  /** @type {any} */(handleTaggedTemplate)[$$strict] = true;
  return handleTaggedTemplate(markup, ...args);
}
 
handleTaggedTemplate.strict = setStrictMode;