# All files / lib html.js

100% Statements 159/159
100% Branches 24/24
100% Functions 3/3
100% Lines 159/159

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
    153
    154
    155
    156
    157
    158
    159
    1602x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    105x
    105x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    217x
    217x
    217x
    217x
    6x
    6x
    211x
    211x
    211x
    211x
    3x
    3x
    211x
    211x
    217x
    118x
    5x
    5x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    118x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    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
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    217x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    14x
    14x
    14x
    14x
    14x
    14x
    12x
    12x
    12x
    14x
    2x
    2x
     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;
      try {
        return handleTaggedTemplate(markup, ...args);
      }
      catch (e) {
        /** @type {any} */(handleTaggedTemplate)[$$strict] = false;
        throw e;
      }
    }

    handleTaggedTemplate.strict = setStrictMode;