private _parseInlineTag()

in tsdoc/src/parser/NodeParser.ts [790:979]


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

    if (tokenReader.peekTokenKind() !== TokenKind.LeftCurlyBracket) {
      return this._backtrackAndCreateError(
        tokenReader,
        marker,
        TSDocMessageId.MissingTag,
        'Expecting a TSDoc tag starting with "{"'
      );
    }
    tokenReader.readToken();

    const openingDelimiterExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();

    // For inline tags, if we handle errors by backtracking to the "{"  token, then the main loop
    // will then interpret the "@" as a block tag, which is almost certainly incorrect.  So the
    // DocErrorText needs to include both the "{" and "@" tokens.
    // We will use _backtrackAndCreateErrorRangeForFailure() for that.
    const atSignMarker: number = tokenReader.createMarker();

    if (tokenReader.peekTokenKind() !== TokenKind.AtSign) {
      return this._backtrackAndCreateError(
        tokenReader,
        marker,
        TSDocMessageId.MalformedInlineTag,
        'Expecting a TSDoc tag starting with "{@"'
      );
    }

    // Include the "@" as part of the tagName
    let tagName: string = tokenReader.readToken().toString();

    while (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
      tagName += tokenReader.readToken().toString();
    }

    if (tagName === '@') {
      // This is an unusual case
      const failure: IFailure = this._createFailureForTokensSince(
        tokenReader,
        TSDocMessageId.MalformedInlineTag,
        'Expecting a TSDoc inline tag name after the "{@" characters',
        atSignMarker
      );
      return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
    }

    if (StringChecks.explainIfInvalidTSDocTagName(tagName)) {
      const failure: IFailure = this._createFailureForTokensSince(
        tokenReader,
        TSDocMessageId.MalformedTagName,
        'A TSDoc tag name must start with a letter and contain only letters and numbers',
        atSignMarker
      );
      return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
    }

    const tagNameExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();

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

    if (spacingAfterTagNameExcerpt === undefined) {
      // If there were no spaces at all, that's an error unless it's the degenerate "{@tag}" case
      if (tokenReader.peekTokenKind() !== TokenKind.RightCurlyBracket) {
        const badCharacter: string = tokenReader.peekToken().range.toString()[0];
        const failure: IFailure = this._createFailureForToken(
          tokenReader,
          TSDocMessageId.CharactersAfterInlineTag,
          `The character ${JSON.stringify(
            badCharacter
          )} cannot appear after the TSDoc tag name; expecting a space`
        );
        return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
      }
    }

    let done: boolean = false;
    while (!done) {
      switch (tokenReader.peekTokenKind()) {
        case TokenKind.EndOfInput:
          return this._backtrackAndCreateErrorRange(
            tokenReader,
            marker,
            atSignMarker,
            TSDocMessageId.InlineTagMissingRightBrace,
            'The TSDoc inline tag name is missing its closing "}"'
          );
        case TokenKind.Backslash:
          // http://usejsdoc.org/about-block-inline-tags.html
          // "If your tag's text includes a closing curly brace (}), you must escape it with
          // a leading backslash (\)."
          tokenReader.readToken(); // discard the backslash

          // In CommonMark, a backslash is only allowed before a punctuation
          // character.  In all other contexts, the backslash is interpreted as a
          // literal character.
          if (!Tokenizer.isPunctuation(tokenReader.peekTokenKind())) {
            const failure: IFailure = this._createFailureForToken(
              tokenReader,
              TSDocMessageId.UnnecessaryBackslash,
              'A backslash can only be used to escape a punctuation character'
            );
            return this._backtrackAndCreateErrorRangeForFailure(
              tokenReader,
              marker,
              atSignMarker,
              'Error reading inline TSDoc tag: ',
              failure
            );
          }

          tokenReader.readToken();
          break;
        case TokenKind.LeftCurlyBracket: {
          const failure: IFailure = this._createFailureForToken(
            tokenReader,
            TSDocMessageId.InlineTagUnescapedBrace,
            'The "{" character must be escaped with a backslash when used inside a TSDoc inline tag'
          );
          return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
        }
        case TokenKind.RightCurlyBracket:
          done = true;
          break;
        default:
          tokenReader.readToken();
          break;
      }
    }

    const tagContentExcerpt: TokenSequence | undefined = tokenReader.tryExtractAccumulatedSequence();

    // Read the right curly bracket
    tokenReader.readToken();
    const closingDelimiterExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();

    const docInlineTagParsedParameters: IDocInlineTagParsedParameters = {
      parsed: true,
      configuration: this._configuration,

      openingDelimiterExcerpt,

      tagNameExcerpt,
      tagName,
      spacingAfterTagNameExcerpt,

      tagContentExcerpt,

      closingDelimiterExcerpt
    };

    const tagNameWithUpperCase: string = tagName.toUpperCase();

    // Create a new TokenReader that will reparse the tokens corresponding to the tagContent.
    const embeddedTokenReader: TokenReader = new TokenReader(
      this._parserContext,
      tagContentExcerpt ? tagContentExcerpt : TokenSequence.createEmpty(this._parserContext)
    );

    let docNode: DocNode;
    switch (tagNameWithUpperCase) {
      case StandardTags.inheritDoc.tagNameWithUpperCase:
        docNode = this._parseInheritDocTag(docInlineTagParsedParameters, embeddedTokenReader);
        break;
      case StandardTags.link.tagNameWithUpperCase:
        docNode = this._parseLinkTag(docInlineTagParsedParameters, embeddedTokenReader);
        break;
      default:
        docNode = new DocInlineTag(docInlineTagParsedParameters);
    }

    // Validate the tag
    const tagDefinition:
      | TSDocTagDefinition
      | undefined = this._parserContext.configuration.tryGetTagDefinitionWithUpperCase(tagNameWithUpperCase);

    this._validateTagDefinition(
      tagDefinition,
      tagName,
      /* expectingInlineTag */ true,
      tagNameExcerpt,
      docNode
    );

    return docNode;
  }