static parse()

in x-parser.js [961:1087]


  static parse(strings, onToken) {
    const stringsLength = strings.length;
    const tagNames = [null];
    let tagName = null;
    let stringsIndex = 0;
    let string = null;
    let stringLength = null;
    let stringIndex = null;
    let nextStringIndex = null;
    let value = XParser.#initial; // Values are stateful regular expressions.

    try {
      while (stringsIndex < stringsLength) {
        XParser.#validateRawString(strings.raw[stringsIndex]);

        string = strings[stringsIndex];
        if (stringsIndex > 0) {
          switch (value) {
            case XParser.#initial:
            case XParser.#boundContent:
            case XParser.#text:
            case XParser.#startTagClose:
            case XParser.#endTag:
              if (tagName === 'textarea') {
                // The textarea tag only accepts text, we restrict interpolation
                //  there. See note on “replaceable character data” in the
                //  following reference document:
                //  https://w3c.github.io/html-reference/syntax.html#text-syntax
                const sloppyStartInterpolation = value !== XParser.#startTagClose;
                XParser.#sendBoundTextTokens(onToken, stringsIndex - 1, string, stringIndex, sloppyStartInterpolation);
              } else {
                XParser.#sendBoundContentTokens(onToken, stringsIndex - 1, string, stringIndex);
              }
              value = XParser.#boundContent;
              break;
          }
        }

        stringLength = string.length;
        stringIndex = 0;
        while (stringIndex < stringLength) {
          // The string will be empty if we have a template like this `${…}${…}`.
          //  See related logic at the end of the inner loop;
          if (string.length > 0) {
            const nextValue = XParser.#validTransition(string, stringIndex, value);
            if (!nextValue) {
              XParser.#throwTransitionError(strings, stringsIndex, string, stringIndex, value);
            }
            value = nextValue;
            nextStringIndex = value.lastIndex;
          }

          // When we transition into certain values, we need to take action.
          switch (value) {
            case XParser.#text:
              XParser.#sendTextTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#comment:
              XParser.#sendCommentTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#startTagOpen:
              tagName = XParser.#sendStartTagOpenTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              tagNames.push(tagName);
              break;
            case XParser.#startTagSpace:
              XParser.#sendStartTagSpaceTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#danglingQuote:
              XParser.#sendDanglingQuoteTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#boolean:
              XParser.#sendBooleanTokens(onToken, tagName, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#attribute:
              XParser.#sendAttributeTokens(onToken, tagName, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#boundBoolean:
              XParser.#sendBoundBooleanTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#boundDefined:
              XParser.#sendBoundDefinedTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#boundAttribute:
              XParser.#sendBoundAttributeTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#boundProperty:
              XParser.#sendBoundPropertyTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
              break;
            case XParser.#startTagClose:
              if (XParser.#voidHtmlElements.has(tagName)) {
                XParser.#sendVoidElementTokens(onToken, stringsIndex, stringIndex, nextStringIndex);
                tagNames.pop();
                tagName = tagNames[tagNames.length - 1];
              } else if (tagName === 'textarea' && XParser.#startTagClose.lastIndex !== string.length) {
                // If successful, move cursor through textarea element end tag.
                nextStringIndex = XParser.#sendTextareaTokens(onToken, stringsIndex, string, stringIndex, nextStringIndex);
                value = XParser.#endTag;
                value.lastIndex = nextStringIndex;
                tagNames.pop();
                tagName = tagNames[tagNames.length - 1];
              } else {
                XParser.#sendStartTagCloseTokens(onToken, stringsIndex, stringIndex, nextStringIndex);
              }
              break;
            case XParser.#endTag: {
              XParser.#sendEndTagTokens(onToken, tagName, strings, stringsIndex, string, stringIndex, nextStringIndex);
              tagNames.pop();
              tagName = tagNames[tagNames.length - 1];
              break;
            }
          }
          stringIndex = nextStringIndex; // Update out pointer from our pattern match.
          nextStringIndex = null;
        }
        stringsIndex++;
      }

      XParser.#validateExit(value, tagName);
    } catch (error) {
      // Roughly match the conventions for “onToken”.
      const index = stringsIndex;
      const start = stringIndex;
      const end = nextStringIndex;
      error[XParser.#errorContextKey] = { index, start, end };
      throw error;
    }
  }