private _parseHtmlStartTag()

in tsdoc/src/parser/NodeParser.ts [1829:1921]


  private _parseHtmlStartTag(tokenReader: TokenReader): DocNode {
    tokenReader.assertAccumulatedSequenceIsEmpty();
    const marker: number = tokenReader.createMarker();

    // Read the "<" delimiter
    const lessThanToken: Token = tokenReader.readToken();
    if (lessThanToken.kind !== TokenKind.LessThan) {
      // This would be a parser bug -- the caller of _parseHtmlStartTag() should have verified this while
      // looking ahead
      throw new Error('Expecting an HTML tag starting with "<"');
    }

    // NOTE: CommonMark does not permit whitespace after the "<"

    const openingDelimiterExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();

    // Read the element name
    const nameExcerpt: ResultOrFailure<TokenSequence> = this._parseHtmlName(tokenReader);
    if (isFailure(nameExcerpt)) {
      return this._backtrackAndCreateErrorForFailure(
        tokenReader,
        marker,
        'Invalid HTML element: ',
        nameExcerpt
      );
    }

    const spacingAfterNameExcerpt: TokenSequence | undefined = this._tryReadSpacingAndNewlines(tokenReader);

    const htmlAttributes: DocHtmlAttribute[] = [];

    // Read the attributes until we see a ">" or "/>"
    while (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
      // Read the attribute
      const attributeNode: ResultOrFailure<DocHtmlAttribute> = this._parseHtmlAttribute(tokenReader);
      if (isFailure(attributeNode)) {
        return this._backtrackAndCreateErrorForFailure(
          tokenReader,
          marker,
          'The HTML element has an invalid attribute: ',
          attributeNode
        );
      }

      htmlAttributes.push(attributeNode);
    }

    // Read the closing "/>" or ">" as the Excerpt.suffix
    tokenReader.assertAccumulatedSequenceIsEmpty();
    const endDelimiterMarker: number = tokenReader.createMarker();

    let selfClosingTag: boolean = false;
    if (tokenReader.peekTokenKind() === TokenKind.Slash) {
      tokenReader.readToken();
      selfClosingTag = true;
    }
    if (tokenReader.peekTokenKind() !== TokenKind.GreaterThan) {
      const failure: IFailure = this._createFailureForTokensSince(
        tokenReader,
        TSDocMessageId.HtmlTagMissingGreaterThan,
        'Expecting an attribute or ">" or "/>"',
        endDelimiterMarker
      );
      return this._backtrackAndCreateErrorForFailure(
        tokenReader,
        marker,
        'The HTML tag has invalid syntax: ',
        failure
      );
    }
    tokenReader.readToken();

    const closingDelimiterExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();

    // NOTE: We don't read excerptParameters.separator here, since if there is any it
    // will be represented as DocPlainText.

    return new DocHtmlStartTag({
      parsed: true,
      configuration: this._configuration,

      openingDelimiterExcerpt,

      nameExcerpt,
      spacingAfterNameExcerpt,

      htmlAttributes,

      selfClosingTag,

      closingDelimiterExcerpt
    });
  }