"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeParser = void 0;
const Token_1 = require("./Token");
const Tokenizer_1 = require("./Tokenizer");
const nodes_1 = require("../nodes");
const TokenSequence_1 = require("./TokenSequence");
const TokenReader_1 = require("./TokenReader");
const StringChecks_1 = require("./StringChecks");
const TSDocTagDefinition_1 = require("../configuration/TSDocTagDefinition");
const StandardTags_1 = require("../details/StandardTags");
const PlainTextEmitter_1 = require("../emitters/PlainTextEmitter");
const TSDocMessageId_1 = require("./TSDocMessageId");
function isFailure(resultOrFailure) {
    return resultOrFailure !== undefined && Object.hasOwnProperty.call(resultOrFailure, 'failureMessage');
}
/**
 * The main parser for TSDoc comments.
 */
class NodeParser {
    constructor(parserContext) {
        this._parserContext = parserContext;
        this._configuration = parserContext.configuration;
        this._currentSection = parserContext.docComment.summarySection;
    }
    parse() {
        const tokenReader = new TokenReader_1.TokenReader(this._parserContext);
        let done = false;
        while (!done) {
            // Extract the next token
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.EndOfInput:
                    done = true;
                    break;
                case Token_1.TokenKind.Newline:
                    this._pushAccumulatedPlainText(tokenReader);
                    tokenReader.readToken();
                    this._pushNode(new nodes_1.DocSoftBreak({
                        parsed: true,
                        configuration: this._configuration,
                        softBreakExcerpt: tokenReader.extractAccumulatedSequence()
                    }));
                    break;
                case Token_1.TokenKind.Backslash:
                    this._pushAccumulatedPlainText(tokenReader);
                    this._pushNode(this._parseBackslashEscape(tokenReader));
                    break;
                case Token_1.TokenKind.AtSign:
                    this._pushAccumulatedPlainText(tokenReader);
                    this._parseAndPushBlock(tokenReader);
                    break;
                case Token_1.TokenKind.LeftCurlyBracket: {
                    this._pushAccumulatedPlainText(tokenReader);
                    const marker = tokenReader.createMarker();
                    const docNode = this._parseInlineTag(tokenReader);
                    const docComment = this._parserContext.docComment;
                    if (docNode instanceof nodes_1.DocInheritDocTag) {
                        // The @inheritDoc tag is irregular because it looks like an inline tag, but
                        // it actually represents the entire comment body
                        const tagEndMarker = tokenReader.createMarker() - 1;
                        if (docComment.inheritDocTag === undefined) {
                            this._parserContext.docComment.inheritDocTag = docNode;
                        }
                        else {
                            this._pushNode(this._backtrackAndCreateErrorRange(tokenReader, marker, tagEndMarker, TSDocMessageId_1.TSDocMessageId.ExtraInheritDocTag, 'A doc comment cannot have more than one @inheritDoc tag'));
                        }
                    }
                    else {
                        this._pushNode(docNode);
                    }
                    break;
                }
                case Token_1.TokenKind.RightCurlyBracket:
                    this._pushAccumulatedPlainText(tokenReader);
                    this._pushNode(this._createError(tokenReader, TSDocMessageId_1.TSDocMessageId.EscapeRightBrace, 'The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag'));
                    break;
                case Token_1.TokenKind.LessThan:
                    this._pushAccumulatedPlainText(tokenReader);
                    // Look ahead two tokens to see if this is "<a>" or "</a>".
                    if (tokenReader.peekTokenAfterKind() === Token_1.TokenKind.Slash) {
                        this._pushNode(this._parseHtmlEndTag(tokenReader));
                    }
                    else {
                        this._pushNode(this._parseHtmlStartTag(tokenReader));
                    }
                    break;
                case Token_1.TokenKind.GreaterThan:
                    this._pushAccumulatedPlainText(tokenReader);
                    this._pushNode(this._createError(tokenReader, TSDocMessageId_1.TSDocMessageId.EscapeGreaterThan, 'The ">" character should be escaped using a backslash to avoid confusion with an HTML tag'));
                    break;
                case Token_1.TokenKind.Backtick:
                    this._pushAccumulatedPlainText(tokenReader);
                    if (tokenReader.peekTokenAfterKind() === Token_1.TokenKind.Backtick &&
                        tokenReader.peekTokenAfterAfterKind() === Token_1.TokenKind.Backtick) {
                        this._pushNode(this._parseFencedCode(tokenReader));
                    }
                    else {
                        this._pushNode(this._parseCodeSpan(tokenReader));
                    }
                    break;
                default:
                    // If nobody recognized this token, then accumulate plain text
                    tokenReader.readToken();
                    break;
            }
        }
        this._pushAccumulatedPlainText(tokenReader);
        this._performValidationChecks();
    }
    _performValidationChecks() {
        const docComment = this._parserContext.docComment;
        if (docComment.deprecatedBlock) {
            if (!PlainTextEmitter_1.PlainTextEmitter.hasAnyTextContent(docComment.deprecatedBlock)) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.MissingDeprecationMessage, `The ${docComment.deprecatedBlock.blockTag.tagName} block must include a deprecation message,` +
                    ` e.g. describing the recommended alternative`, docComment.deprecatedBlock.blockTag.getTokenSequence(), docComment.deprecatedBlock);
            }
        }
        if (docComment.inheritDocTag) {
            if (docComment.remarksBlock) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.InheritDocIncompatibleTag, `A "${docComment.remarksBlock.blockTag.tagName}" block must not be used, because that` +
                    ` content is provided by the @inheritDoc tag`, docComment.remarksBlock.blockTag.getTokenSequence(), docComment.remarksBlock.blockTag);
            }
            if (PlainTextEmitter_1.PlainTextEmitter.hasAnyTextContent(docComment.summarySection)) {
                this._parserContext.log.addMessageForTextRange(TSDocMessageId_1.TSDocMessageId.InheritDocIncompatibleSummary, 'The summary section must not have any content, because that' +
                    ' content is provided by the @inheritDoc tag', this._parserContext.commentRange);
            }
        }
    }
    _validateTagDefinition(tagDefinition, tagName, expectingInlineTag, tokenSequenceForErrorContext, nodeForErrorContext) {
        if (tagDefinition) {
            const isInlineTag = tagDefinition.syntaxKind === TSDocTagDefinition_1.TSDocTagSyntaxKind.InlineTag;
            if (isInlineTag !== expectingInlineTag) {
                // The tag is defined, but it is used incorrectly
                if (expectingInlineTag) {
                    this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.TagShouldNotHaveBraces, `The TSDoc tag "${tagName}" is not an inline tag; it must not be enclosed in "{ }" braces`, tokenSequenceForErrorContext, nodeForErrorContext);
                }
                else {
                    this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.InlineTagMissingBraces, `The TSDoc tag "${tagName}" is an inline tag; it must be enclosed in "{ }" braces`, tokenSequenceForErrorContext, nodeForErrorContext);
                }
            }
            else {
                if (this._parserContext.configuration.validation.reportUnsupportedTags) {
                    if (!this._parserContext.configuration.isTagSupported(tagDefinition)) {
                        // The tag is defined, but not supported
                        this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.UnsupportedTag, `The TSDoc tag "${tagName}" is not supported by this tool`, tokenSequenceForErrorContext, nodeForErrorContext);
                    }
                }
            }
        }
        else {
            // The tag is not defined
            if (!this._parserContext.configuration.validation.ignoreUndefinedTags) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.UndefinedTag, `The TSDoc tag "${tagName}" is not defined in this configuration`, tokenSequenceForErrorContext, nodeForErrorContext);
            }
        }
    }
    _pushAccumulatedPlainText(tokenReader) {
        if (!tokenReader.isAccumulatedSequenceEmpty()) {
            this._pushNode(new nodes_1.DocPlainText({
                parsed: true,
                configuration: this._configuration,
                textExcerpt: tokenReader.extractAccumulatedSequence()
            }));
        }
    }
    _parseAndPushBlock(tokenReader) {
        const docComment = this._parserContext.docComment;
        const configuration = this._parserContext.configuration;
        const modifierTagSet = docComment.modifierTagSet;
        const parsedBlockTag = this._parseBlockTag(tokenReader);
        if (parsedBlockTag.kind !== nodes_1.DocNodeKind.BlockTag) {
            this._pushNode(parsedBlockTag);
            return;
        }
        const docBlockTag = parsedBlockTag;
        // Do we have a definition for this tag?
        const tagDefinition = configuration.tryGetTagDefinitionWithUpperCase(docBlockTag.tagNameWithUpperCase);
        this._validateTagDefinition(tagDefinition, docBlockTag.tagName, 
        /* expectingInlineTag */ false, docBlockTag.getTokenSequence(), docBlockTag);
        if (tagDefinition) {
            switch (tagDefinition.syntaxKind) {
                case TSDocTagDefinition_1.TSDocTagSyntaxKind.BlockTag:
                    if (docBlockTag.tagNameWithUpperCase === StandardTags_1.StandardTags.param.tagNameWithUpperCase) {
                        const docParamBlock = this._parseParamBlock(tokenReader, docBlockTag, StandardTags_1.StandardTags.param.tagName);
                        this._parserContext.docComment.params.add(docParamBlock);
                        this._currentSection = docParamBlock.content;
                        return;
                    }
                    else if (docBlockTag.tagNameWithUpperCase === StandardTags_1.StandardTags.typeParam.tagNameWithUpperCase) {
                        const docParamBlock = this._parseParamBlock(tokenReader, docBlockTag, StandardTags_1.StandardTags.typeParam.tagName);
                        this._parserContext.docComment.typeParams.add(docParamBlock);
                        this._currentSection = docParamBlock.content;
                        return;
                    }
                    else {
                        const newBlock = new nodes_1.DocBlock({
                            configuration: this._configuration,
                            blockTag: docBlockTag
                        });
                        this._addBlockToDocComment(newBlock);
                        this._currentSection = newBlock.content;
                    }
                    return;
                case TSDocTagDefinition_1.TSDocTagSyntaxKind.ModifierTag:
                    // The block tag was recognized as a modifier, so add it to the modifier tag set
                    // and do NOT call currentSection.appendNode(parsedNode)
                    modifierTagSet.addTag(docBlockTag);
                    return;
            }
        }
        this._pushNode(docBlockTag);
    }
    _addBlockToDocComment(block) {
        const docComment = this._parserContext.docComment;
        switch (block.blockTag.tagNameWithUpperCase) {
            case StandardTags_1.StandardTags.remarks.tagNameWithUpperCase:
                docComment.remarksBlock = block;
                break;
            case StandardTags_1.StandardTags.privateRemarks.tagNameWithUpperCase:
                docComment.privateRemarks = block;
                break;
            case StandardTags_1.StandardTags.deprecated.tagNameWithUpperCase:
                docComment.deprecatedBlock = block;
                break;
            case StandardTags_1.StandardTags.returns.tagNameWithUpperCase:
                docComment.returnsBlock = block;
                break;
            case StandardTags_1.StandardTags.see.tagNameWithUpperCase:
                docComment._appendSeeBlock(block);
                break;
            default:
                docComment.appendCustomBlock(block);
                break;
        }
    }
    /**
     * Used by `_parseParamBlock()`, this parses a JSDoc expression remainder like `string}` or `="]"]` from
     * an input like `@param {string} [x="]"] - the X value`.  It detects nested balanced pairs of delimiters
     * and escaped string literals.
     */
    _tryParseJSDocTypeOrValueRest(tokenReader, openKind, closeKind, startMarker) {
        let quoteKind;
        let openCount = 1;
        while (openCount > 0) {
            let tokenKind = tokenReader.peekTokenKind();
            switch (tokenKind) {
                case openKind:
                    // ignore open bracket/brace inside of a quoted string
                    if (quoteKind === undefined)
                        openCount++;
                    break;
                case closeKind:
                    // ignore close bracket/brace inside of a quoted string
                    if (quoteKind === undefined)
                        openCount--;
                    break;
                case Token_1.TokenKind.Backslash:
                    // ignore backslash outside of quoted string
                    if (quoteKind !== undefined) {
                        // skip the backslash and the next character.
                        tokenReader.readToken();
                        tokenKind = tokenReader.peekTokenKind();
                    }
                    break;
                case Token_1.TokenKind.DoubleQuote:
                case Token_1.TokenKind.SingleQuote:
                case Token_1.TokenKind.Backtick:
                    if (quoteKind === tokenKind) {
                        // exit quoted string if quote character matches.
                        quoteKind = undefined;
                    }
                    else if (quoteKind === undefined) {
                        // start quoted string if not in a quoted string.
                        quoteKind = tokenKind;
                    }
                    break;
            }
            // give up at end of input and backtrack to start.
            if (tokenKind === Token_1.TokenKind.EndOfInput) {
                tokenReader.backtrackToMarker(startMarker);
                return undefined;
            }
            tokenReader.readToken();
        }
        return tokenReader.tryExtractAccumulatedSequence();
    }
    /**
     * Used by `_parseParamBlock()`, this parses a JSDoc expression like `{string}` from
     * an input like `@param {string} x - the X value`.
     */
    _tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        // do not parse `{@...` as a JSDoc type
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.LeftCurlyBracket ||
            tokenReader.peekTokenAfterKind() === Token_1.TokenKind.AtSign) {
            return undefined;
        }
        const startMarker = tokenReader.createMarker();
        tokenReader.readToken(); // read the "{"
        let jsdocTypeExcerpt = this._tryParseJSDocTypeOrValueRest(tokenReader, Token_1.TokenKind.LeftCurlyBracket, Token_1.TokenKind.RightCurlyBracket, startMarker);
        if (jsdocTypeExcerpt) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ParamTagWithInvalidType, 'The ' + tagName + " block should not include a JSDoc-style '{type}'", jsdocTypeExcerpt, docBlockTag);
            const spacingAfterJsdocTypeExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
            if (spacingAfterJsdocTypeExcerpt) {
                jsdocTypeExcerpt = jsdocTypeExcerpt.getNewSequence(jsdocTypeExcerpt.startIndex, spacingAfterJsdocTypeExcerpt.endIndex);
            }
        }
        return jsdocTypeExcerpt;
    }
    /**
     * Used by `_parseParamBlock()`, this parses a JSDoc expression remainder like `=[]]` from
     * an input like `@param {string} [x=[]] - the X value`.
     */
    _tryParseJSDocOptionalNameRest(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) {
            const startMarker = tokenReader.createMarker();
            return this._tryParseJSDocTypeOrValueRest(tokenReader, Token_1.TokenKind.LeftSquareBracket, Token_1.TokenKind.RightSquareBracket, startMarker);
        }
        return undefined;
    }
    _parseParamBlock(tokenReader, docBlockTag, tagName) {
        const startMarker = tokenReader.createMarker();
        const spacingBeforeParameterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Skip past a JSDoc type (i.e., '@param {type} paramName') if found, and report a warning.
        const unsupportedJsdocTypeBeforeParameterNameExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName);
        // Parse opening of invalid JSDoc optional parameter name (e.g., '[')
        let unsupportedJsdocOptionalNameOpenBracketExcerpt;
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.LeftSquareBracket) {
            tokenReader.readToken(); // read the "["
            unsupportedJsdocOptionalNameOpenBracketExcerpt = tokenReader.extractAccumulatedSequence();
        }
        let parameterName = '';
        let done = false;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.AsciiWord:
                case Token_1.TokenKind.Period:
                case Token_1.TokenKind.DollarSign:
                    parameterName += tokenReader.readToken();
                    break;
                default:
                    done = true;
                    break;
            }
        }
        const explanation = StringChecks_1.StringChecks.explainIfInvalidUnquotedIdentifier(parameterName);
        if (explanation !== undefined) {
            tokenReader.backtrackToMarker(startMarker);
            const errorParamBlock = new nodes_1.DocParamBlock({
                configuration: this._configuration,
                blockTag: docBlockTag,
                parameterName: ''
            });
            const errorMessage = parameterName.length > 0
                ? 'The ' + tagName + ' block should be followed by a valid parameter name: ' + explanation
                : 'The ' + tagName + ' block should be followed by a parameter name';
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ParamTagWithInvalidName, errorMessage, docBlockTag.getTokenSequence(), docBlockTag);
            return errorParamBlock;
        }
        const parameterNameExcerpt = tokenReader.extractAccumulatedSequence();
        // Parse closing of invalid JSDoc optional parameter name (e.g., ']', '=default]').
        let unsupportedJsdocOptionalNameRestExcerpt;
        if (unsupportedJsdocOptionalNameOpenBracketExcerpt) {
            unsupportedJsdocOptionalNameRestExcerpt = this._tryParseJSDocOptionalNameRest(tokenReader);
            let errorSequence = unsupportedJsdocOptionalNameOpenBracketExcerpt;
            if (unsupportedJsdocOptionalNameRestExcerpt) {
                errorSequence = docBlockTag
                    .getTokenSequence()
                    .getNewSequence(unsupportedJsdocOptionalNameOpenBracketExcerpt.startIndex, unsupportedJsdocOptionalNameRestExcerpt.endIndex);
            }
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ParamTagWithInvalidOptionalName, 'The ' +
                tagName +
                " should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.", errorSequence, docBlockTag);
        }
        const spacingAfterParameterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Skip past a trailing JSDoc type (i.e., '@param paramName {type}') if found, and report a warning.
        const unsupportedJsdocTypeAfterParameterNameExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName);
        // TODO: Warn if there is no space before or after the hyphen
        let hyphenExcerpt;
        let spacingAfterHyphenExcerpt;
        let unsupportedJsdocTypeAfterHyphenExcerpt;
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.Hyphen) {
            tokenReader.readToken();
            hyphenExcerpt = tokenReader.extractAccumulatedSequence();
            // TODO: Only read one space
            spacingAfterHyphenExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
            // Skip past a JSDoc type (i.e., '@param paramName - {type}') if found, and report a warning.
            unsupportedJsdocTypeAfterHyphenExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName);
        }
        else {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ParamTagMissingHyphen, 'The ' + tagName + ' block should be followed by a parameter name and then a hyphen', docBlockTag.getTokenSequence(), docBlockTag);
        }
        return new nodes_1.DocParamBlock({
            parsed: true,
            configuration: this._configuration,
            blockTag: docBlockTag,
            spacingBeforeParameterNameExcerpt,
            unsupportedJsdocTypeBeforeParameterNameExcerpt,
            unsupportedJsdocOptionalNameOpenBracketExcerpt,
            parameterNameExcerpt,
            parameterName,
            unsupportedJsdocOptionalNameRestExcerpt,
            spacingAfterParameterNameExcerpt,
            unsupportedJsdocTypeAfterParameterNameExcerpt,
            hyphenExcerpt,
            spacingAfterHyphenExcerpt,
            unsupportedJsdocTypeAfterHyphenExcerpt
        });
    }
    _pushNode(docNode) {
        if (this._configuration.docNodeManager.isAllowedChild(nodes_1.DocNodeKind.Paragraph, docNode.kind)) {
            this._currentSection.appendNodeInParagraph(docNode);
        }
        else {
            this._currentSection.appendNode(docNode);
        }
    }
    _parseBackslashEscape(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const marker = tokenReader.createMarker();
        tokenReader.readToken(); // read the backslash
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.EndOfInput) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.UnnecessaryBackslash, 'A backslash must precede another character that is being escaped');
        }
        const escapedToken = tokenReader.readToken(); // read the escaped character
        // 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_1.Tokenizer.isPunctuation(escapedToken.kind)) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.UnnecessaryBackslash, 'A backslash can only be used to escape a punctuation character');
        }
        const encodedTextExcerpt = tokenReader.extractAccumulatedSequence();
        return new nodes_1.DocEscapedText({
            parsed: true,
            configuration: this._configuration,
            escapeStyle: nodes_1.EscapeStyle.CommonMarkBackslash,
            encodedTextExcerpt,
            decodedText: escapedToken.toString()
        });
    }
    _parseBlockTag(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const marker = tokenReader.createMarker();
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.AtSign) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.MissingTag, 'Expecting a TSDoc tag starting with "@"');
        }
        // "@one" is a valid TSDoc tag at the start of a line, but "@one@two" is
        // a syntax error.  For two tags it should be "@one @two", or for literal text it
        // should be "\@one\@two".
        switch (tokenReader.peekPreviousTokenKind()) {
            case Token_1.TokenKind.EndOfInput:
            case Token_1.TokenKind.Spacing:
            case Token_1.TokenKind.Newline:
                break;
            default:
                return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.AtSignInWord, 'The "@" character looks like part of a TSDoc tag; use a backslash to escape it');
        }
        // Include the "@" as part of the tagName
        let tagName = tokenReader.readToken().toString();
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.AsciiWord) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.AtSignWithoutTagName, 'Expecting a TSDoc tag name after "@"; if it is not a tag, use a backslash to escape this character');
        }
        const tagNameMarker = tokenReader.createMarker();
        while (tokenReader.peekTokenKind() === Token_1.TokenKind.AsciiWord) {
            tagName += tokenReader.readToken().toString();
        }
        switch (tokenReader.peekTokenKind()) {
            case Token_1.TokenKind.Spacing:
            case Token_1.TokenKind.Newline:
            case Token_1.TokenKind.EndOfInput:
                break;
            default:
                const badCharacter = tokenReader.peekToken().range.toString()[0];
                return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.CharactersAfterBlockTag, `The token "${tagName}" looks like a TSDoc tag but contains an invalid character` +
                    ` ${JSON.stringify(badCharacter)}; if it is not a tag, use a backslash to escape the "@"`);
        }
        if (StringChecks_1.StringChecks.explainIfInvalidTSDocTagName(tagName)) {
            const failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId_1.TSDocMessageId.MalformedTagName, 'A TSDoc tag name must start with a letter and contain only letters and numbers', tagNameMarker);
            return this._backtrackAndCreateErrorForFailure(tokenReader, marker, '', failure);
        }
        return new nodes_1.DocBlockTag({
            parsed: true,
            configuration: this._configuration,
            tagName,
            tagNameExcerpt: tokenReader.extractAccumulatedSequence()
        });
    }
    _parseInlineTag(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const marker = tokenReader.createMarker();
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.LeftCurlyBracket) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.MissingTag, 'Expecting a TSDoc tag starting with "{"');
        }
        tokenReader.readToken();
        const openingDelimiterExcerpt = 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 = tokenReader.createMarker();
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.AtSign) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.MalformedInlineTag, 'Expecting a TSDoc tag starting with "{@"');
        }
        // Include the "@" as part of the tagName
        let tagName = tokenReader.readToken().toString();
        while (tokenReader.peekTokenKind() === Token_1.TokenKind.AsciiWord) {
            tagName += tokenReader.readToken().toString();
        }
        if (tagName === '@') {
            // This is an unusual case
            const failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId_1.TSDocMessageId.MalformedInlineTag, 'Expecting a TSDoc inline tag name after the "{@" characters', atSignMarker);
            return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
        }
        if (StringChecks_1.StringChecks.explainIfInvalidTSDocTagName(tagName)) {
            const failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId_1.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 = tokenReader.extractAccumulatedSequence();
        const spacingAfterTagNameExcerpt = 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() !== Token_1.TokenKind.RightCurlyBracket) {
                const badCharacter = tokenReader.peekToken().range.toString()[0];
                const failure = this._createFailureForToken(tokenReader, TSDocMessageId_1.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 = false;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.EndOfInput:
                    return this._backtrackAndCreateErrorRange(tokenReader, marker, atSignMarker, TSDocMessageId_1.TSDocMessageId.InlineTagMissingRightBrace, 'The TSDoc inline tag name is missing its closing "}"');
                case Token_1.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_1.Tokenizer.isPunctuation(tokenReader.peekTokenKind())) {
                        const failure = this._createFailureForToken(tokenReader, TSDocMessageId_1.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 Token_1.TokenKind.LeftCurlyBracket: {
                    const failure = this._createFailureForToken(tokenReader, TSDocMessageId_1.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 Token_1.TokenKind.RightCurlyBracket:
                    done = true;
                    break;
                default:
                    tokenReader.readToken();
                    break;
            }
        }
        const tagContentExcerpt = tokenReader.tryExtractAccumulatedSequence();
        // Read the right curly bracket
        tokenReader.readToken();
        const closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
        const docInlineTagParsedParameters = {
            parsed: true,
            configuration: this._configuration,
            openingDelimiterExcerpt,
            tagNameExcerpt,
            tagName,
            spacingAfterTagNameExcerpt,
            tagContentExcerpt,
            closingDelimiterExcerpt
        };
        const tagNameWithUpperCase = tagName.toUpperCase();
        // Create a new TokenReader that will reparse the tokens corresponding to the tagContent.
        const embeddedTokenReader = new TokenReader_1.TokenReader(this._parserContext, tagContentExcerpt ? tagContentExcerpt : TokenSequence_1.TokenSequence.createEmpty(this._parserContext));
        let docNode;
        switch (tagNameWithUpperCase) {
            case StandardTags_1.StandardTags.inheritDoc.tagNameWithUpperCase:
                docNode = this._parseInheritDocTag(docInlineTagParsedParameters, embeddedTokenReader);
                break;
            case StandardTags_1.StandardTags.link.tagNameWithUpperCase:
                docNode = this._parseLinkTag(docInlineTagParsedParameters, embeddedTokenReader);
                break;
            default:
                docNode = new nodes_1.DocInlineTag(docInlineTagParsedParameters);
        }
        // Validate the tag
        const tagDefinition = this._parserContext.configuration.tryGetTagDefinitionWithUpperCase(tagNameWithUpperCase);
        this._validateTagDefinition(tagDefinition, tagName, 
        /* expectingInlineTag */ true, tagNameExcerpt, docNode);
        return docNode;
    }
    _parseInheritDocTag(docInlineTagParsedParameters, embeddedTokenReader) {
        // If an error occurs, then return a generic DocInlineTag instead of DocInheritDocTag
        const errorTag = new nodes_1.DocInlineTag(docInlineTagParsedParameters);
        const parameters = {
            ...docInlineTagParsedParameters
        };
        if (embeddedTokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) {
            parameters.declarationReference = this._parseDeclarationReference(embeddedTokenReader, docInlineTagParsedParameters.tagNameExcerpt, errorTag);
            if (!parameters.declarationReference) {
                return errorTag;
            }
            if (embeddedTokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) {
                embeddedTokenReader.readToken();
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.InheritDocTagSyntax, 'Unexpected character after declaration reference', embeddedTokenReader.extractAccumulatedSequence(), errorTag);
                return errorTag;
            }
        }
        return new nodes_1.DocInheritDocTag(parameters);
    }
    _parseLinkTag(docInlineTagParsedParameters, embeddedTokenReader) {
        // If an error occurs, then return a generic DocInlineTag instead of DocInheritDocTag
        const errorTag = new nodes_1.DocInlineTag(docInlineTagParsedParameters);
        const parameters = {
            ...docInlineTagParsedParameters
        };
        if (!docInlineTagParsedParameters.tagContentExcerpt) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.LinkTagEmpty, 'The @link tag content is missing', parameters.tagNameExcerpt, errorTag);
            return errorTag;
        }
        // Is the link destination a URL or a declaration reference?
        //
        // The JSDoc "@link" tag allows URLs, however supporting full URLs would be highly
        // ambiguous, for example "microsoft.windows.camera:" is an actual valid URI scheme,
        // and even the common "mailto:example.com" looks suspiciously like a declaration reference.
        // In practice JSDoc URLs are nearly always HTTP or HTTPS, so it seems fairly reasonable to
        // require the URL to have "://" and a scheme without any punctuation in it.  If a more exotic
        // URL is needed, the HTML "<a>" tag can always be used.
        // We start with a fairly broad classifier heuristic, and then the parsers will refine this:
        // 1. Does it start with "//"?
        // 2. Does it contain "://"?
        let looksLikeUrl = embeddedTokenReader.peekTokenKind() === Token_1.TokenKind.Slash &&
            embeddedTokenReader.peekTokenAfterKind() === Token_1.TokenKind.Slash;
        const marker = embeddedTokenReader.createMarker();
        let done = looksLikeUrl;
        while (!done) {
            switch (embeddedTokenReader.peekTokenKind()) {
                // An URI scheme can contain letters, numbers, minus, plus, and periods
                case Token_1.TokenKind.AsciiWord:
                case Token_1.TokenKind.Period:
                case Token_1.TokenKind.Hyphen:
                case Token_1.TokenKind.Plus:
                    embeddedTokenReader.readToken();
                    break;
                case Token_1.TokenKind.Colon:
                    embeddedTokenReader.readToken();
                    // Once we a reach a colon, then it's a URL only if we see "://"
                    looksLikeUrl =
                        embeddedTokenReader.peekTokenKind() === Token_1.TokenKind.Slash &&
                            embeddedTokenReader.peekTokenAfterKind() === Token_1.TokenKind.Slash;
                    done = true;
                    break;
                default:
                    done = true;
            }
        }
        embeddedTokenReader.backtrackToMarker(marker);
        // Is the hyperlink a URL or a declaration reference?
        if (looksLikeUrl) {
            // It starts with something like "http://", so parse it as a URL
            if (!this._parseLinkTagUrlDestination(embeddedTokenReader, parameters, docInlineTagParsedParameters.tagNameExcerpt, errorTag)) {
                return errorTag;
            }
        }
        else {
            // Otherwise, assume it's a declaration reference
            if (!this._parseLinkTagCodeDestination(embeddedTokenReader, parameters, docInlineTagParsedParameters.tagNameExcerpt, errorTag)) {
                return errorTag;
            }
        }
        if (embeddedTokenReader.peekTokenKind() === Token_1.TokenKind.Spacing) {
            // The above parser rules should have consumed any spacing before the pipe
            throw new Error('Unconsumed spacing encountered after construct');
        }
        if (embeddedTokenReader.peekTokenKind() === Token_1.TokenKind.Pipe) {
            // Read the link text
            embeddedTokenReader.readToken();
            parameters.pipeExcerpt = embeddedTokenReader.extractAccumulatedSequence();
            parameters.spacingAfterPipeExcerpt = this._tryReadSpacingAndNewlines(embeddedTokenReader);
            // Read everything until the end
            // NOTE: Because we're using an embedded TokenReader, the TokenKind.EndOfInput occurs
            // when we reach the "}", not the end of the original input
            done = false;
            let spacingAfterLinkTextMarker = undefined;
            while (!done) {
                switch (embeddedTokenReader.peekTokenKind()) {
                    case Token_1.TokenKind.EndOfInput:
                        done = true;
                        break;
                    case Token_1.TokenKind.Pipe:
                    case Token_1.TokenKind.LeftCurlyBracket:
                        const badCharacter = embeddedTokenReader.readToken().toString();
                        this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.LinkTagUnescapedText, `The "${badCharacter}" character may not be used in the link text without escaping it`, embeddedTokenReader.extractAccumulatedSequence(), errorTag);
                        return errorTag;
                    case Token_1.TokenKind.Spacing:
                    case Token_1.TokenKind.Newline:
                        embeddedTokenReader.readToken();
                        break;
                    default:
                        // We found a non-spacing character, so move the spacingAfterLinkTextMarker
                        spacingAfterLinkTextMarker = embeddedTokenReader.createMarker() + 1;
                        embeddedTokenReader.readToken();
                }
            }
            const linkTextAndSpacing = embeddedTokenReader.tryExtractAccumulatedSequence();
            if (linkTextAndSpacing) {
                if (spacingAfterLinkTextMarker === undefined) {
                    // We never found any non-spacing characters, so everything is trailing spacing
                    parameters.spacingAfterLinkTextExcerpt = linkTextAndSpacing;
                }
                else if (spacingAfterLinkTextMarker >= linkTextAndSpacing.endIndex) {
                    // We found no trailing spacing, so everything we found is the text
                    parameters.linkTextExcerpt = linkTextAndSpacing;
                }
                else {
                    // Split the trailing spacing from the link text
                    parameters.linkTextExcerpt = linkTextAndSpacing.getNewSequence(linkTextAndSpacing.startIndex, spacingAfterLinkTextMarker);
                    parameters.spacingAfterLinkTextExcerpt = linkTextAndSpacing.getNewSequence(spacingAfterLinkTextMarker, linkTextAndSpacing.endIndex);
                }
            }
        }
        else if (embeddedTokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) {
            embeddedTokenReader.readToken();
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.LinkTagDestinationSyntax, 'Unexpected character after link destination', embeddedTokenReader.extractAccumulatedSequence(), errorTag);
            return errorTag;
        }
        return new nodes_1.DocLinkTag(parameters);
    }
    _parseLinkTagUrlDestination(embeddedTokenReader, parameters, tokenSequenceForErrorContext, nodeForErrorContext) {
        // Simply accumulate everything up to the next space. We won't try to implement a proper
        // URI parser here.
        let urlDestination = '';
        let done = false;
        while (!done) {
            switch (embeddedTokenReader.peekTokenKind()) {
                case Token_1.TokenKind.Spacing:
                case Token_1.TokenKind.Newline:
                case Token_1.TokenKind.EndOfInput:
                case Token_1.TokenKind.Pipe:
                case Token_1.TokenKind.RightCurlyBracket:
                    done = true;
                    break;
                default:
                    urlDestination += embeddedTokenReader.readToken();
                    break;
            }
        }
        if (urlDestination.length === 0) {
            // This should be impossible since the caller ensures that peekTokenKind() === TokenKind.AsciiWord
            throw new Error('Missing URL in _parseLinkTagUrlDestination()');
        }
        const urlDestinationExcerpt = embeddedTokenReader.extractAccumulatedSequence();
        const invalidUrlExplanation = StringChecks_1.StringChecks.explainIfInvalidLinkUrl(urlDestination);
        if (invalidUrlExplanation) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.LinkTagInvalidUrl, invalidUrlExplanation, urlDestinationExcerpt, nodeForErrorContext);
            return false;
        }
        parameters.urlDestinationExcerpt = urlDestinationExcerpt;
        parameters.spacingAfterDestinationExcerpt = this._tryReadSpacingAndNewlines(embeddedTokenReader);
        return true;
    }
    _parseLinkTagCodeDestination(embeddedTokenReader, parameters, tokenSequenceForErrorContext, nodeForErrorContext) {
        parameters.codeDestination = this._parseDeclarationReference(embeddedTokenReader, tokenSequenceForErrorContext, nodeForErrorContext);
        return !!parameters.codeDestination;
    }
    _parseDeclarationReference(tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        // The package name can contain characters that look like a member reference.  This means we need to scan forwards
        // to see if there is a "#".  However, we need to be careful not to match a "#" that is part of a quoted expression.
        const marker = tokenReader.createMarker();
        let hasHash = false;
        // A common mistake is to forget the "#" for package name or import path.  The telltale sign
        // of this is mistake is that we see path-only characters such as "@" or "/" in the beginning
        // where this would be a syntax error for a member reference.
        let lookingForImportCharacters = true;
        let sawImportCharacters = false;
        let done = false;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.DoubleQuote:
                case Token_1.TokenKind.EndOfInput:
                case Token_1.TokenKind.LeftCurlyBracket:
                case Token_1.TokenKind.LeftParenthesis:
                case Token_1.TokenKind.LeftSquareBracket:
                case Token_1.TokenKind.Newline:
                case Token_1.TokenKind.Pipe:
                case Token_1.TokenKind.RightCurlyBracket:
                case Token_1.TokenKind.RightParenthesis:
                case Token_1.TokenKind.RightSquareBracket:
                case Token_1.TokenKind.SingleQuote:
                case Token_1.TokenKind.Spacing:
                    done = true;
                    break;
                case Token_1.TokenKind.PoundSymbol:
                    hasHash = true;
                    done = true;
                    break;
                case Token_1.TokenKind.Slash:
                case Token_1.TokenKind.AtSign:
                    if (lookingForImportCharacters) {
                        sawImportCharacters = true;
                    }
                    tokenReader.readToken();
                    break;
                case Token_1.TokenKind.AsciiWord:
                case Token_1.TokenKind.Period:
                case Token_1.TokenKind.Hyphen:
                    // It's a character that looks like part of a package name or import path,
                    // so don't set lookingForImportCharacters = false
                    tokenReader.readToken();
                    break;
                default:
                    // Once we reach something other than AsciiWord and Period, then the meaning of
                    // slashes and at-signs is no longer obvious.
                    lookingForImportCharacters = false;
                    tokenReader.readToken();
            }
        }
        if (!hasHash && sawImportCharacters) {
            // We saw characters that will be a syntax error if interpreted as a member reference,
            // but would make sense as a package name or import path, but we did not find a "#"
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingHash, 'The declaration reference appears to contain a package name or import path,' +
                ' but it is missing the "#" delimiter', tokenReader.extractAccumulatedSequence(), nodeForErrorContext);
            return undefined;
        }
        tokenReader.backtrackToMarker(marker);
        let packageNameExcerpt;
        let importPathExcerpt;
        let importHashExcerpt;
        let spacingAfterImportHashExcerpt;
        if (hasHash) {
            // If it starts with a "." then it's a relative path, not a package name
            if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Period) {
                // Read the package name:
                const scopedPackageName = tokenReader.peekTokenKind() === Token_1.TokenKind.AtSign;
                let finishedScope = false;
                done = false;
                while (!done) {
                    switch (tokenReader.peekTokenKind()) {
                        case Token_1.TokenKind.EndOfInput:
                            // If hasHash=true, then we are expecting to stop when we reach the hash
                            throw new Error('Expecting pound symbol');
                        case Token_1.TokenKind.Slash:
                            // Stop at the first slash, unless this is a scoped package, in which case we stop at the second slash
                            if (scopedPackageName && !finishedScope) {
                                tokenReader.readToken();
                                finishedScope = true;
                            }
                            else {
                                done = true;
                            }
                            break;
                        case Token_1.TokenKind.PoundSymbol:
                            done = true;
                            break;
                        default:
                            tokenReader.readToken();
                    }
                }
                if (!tokenReader.isAccumulatedSequenceEmpty()) {
                    packageNameExcerpt = tokenReader.extractAccumulatedSequence();
                    // Check that the packageName is syntactically valid
                    const explanation = StringChecks_1.StringChecks.explainIfInvalidPackageName(packageNameExcerpt.toString());
                    if (explanation) {
                        this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMalformedPackageName, explanation, packageNameExcerpt, nodeForErrorContext);
                        return undefined;
                    }
                }
            }
            // Read the import path:
            done = false;
            while (!done) {
                switch (tokenReader.peekTokenKind()) {
                    case Token_1.TokenKind.EndOfInput:
                        // If hasHash=true, then we are expecting to stop when we reach the hash
                        throw new Error('Expecting pound symbol');
                    case Token_1.TokenKind.PoundSymbol:
                        done = true;
                        break;
                    default:
                        tokenReader.readToken();
                }
            }
            if (!tokenReader.isAccumulatedSequenceEmpty()) {
                importPathExcerpt = tokenReader.extractAccumulatedSequence();
                // Check that the importPath is syntactically valid
                const explanation = StringChecks_1.StringChecks.explainIfInvalidImportPath(importPathExcerpt.toString(), !!packageNameExcerpt);
                if (explanation) {
                    this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMalformedImportPath, explanation, importPathExcerpt, nodeForErrorContext);
                    return undefined;
                }
            }
            // Read the import hash
            if (tokenReader.peekTokenKind() !== Token_1.TokenKind.PoundSymbol) {
                // The above logic should have left us at the PoundSymbol
                throw new Error('Expecting pound symbol');
            }
            tokenReader.readToken();
            importHashExcerpt = tokenReader.extractAccumulatedSequence();
            spacingAfterImportHashExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
            if (packageNameExcerpt === undefined && importPathExcerpt === undefined) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceHashSyntax, 'The hash character must be preceded by a package name or import path', importHashExcerpt, nodeForErrorContext);
                return undefined;
            }
        }
        // Read the member references:
        const memberReferences = [];
        done = false;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.Period:
                case Token_1.TokenKind.LeftParenthesis:
                case Token_1.TokenKind.AsciiWord:
                case Token_1.TokenKind.Colon:
                case Token_1.TokenKind.LeftSquareBracket:
                case Token_1.TokenKind.DoubleQuote:
                    const expectingDot = memberReferences.length > 0;
                    const memberReference = this._parseMemberReference(tokenReader, expectingDot, tokenSequenceForErrorContext, nodeForErrorContext);
                    if (!memberReference) {
                        return undefined;
                    }
                    memberReferences.push(memberReference);
                    break;
                default:
                    done = true;
            }
        }
        if (packageNameExcerpt === undefined &&
            importPathExcerpt === undefined &&
            memberReferences.length === 0) {
            // We didn't find any parts of a declaration reference
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.MissingReference, 'Expecting a declaration reference', tokenSequenceForErrorContext, nodeForErrorContext);
            return undefined;
        }
        return new nodes_1.DocDeclarationReference({
            parsed: true,
            configuration: this._configuration,
            packageNameExcerpt,
            importPathExcerpt,
            importHashExcerpt,
            spacingAfterImportHashExcerpt,
            memberReferences
        });
    }
    _parseMemberReference(tokenReader, expectingDot, tokenSequenceForErrorContext, nodeForErrorContext) {
        const parameters = {
            parsed: true,
            configuration: this._configuration
        };
        // Read the dot operator
        if (expectingDot) {
            if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Period) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingDot, 'Expecting a period before the next component of a declaration reference', tokenSequenceForErrorContext, nodeForErrorContext);
                return undefined;
            }
            tokenReader.readToken();
            parameters.dotExcerpt = tokenReader.extractAccumulatedSequence();
            parameters.spacingAfterDotExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        }
        // Read the left parenthesis if there is one
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.LeftParenthesis) {
            tokenReader.readToken();
            parameters.leftParenthesisExcerpt = tokenReader.extractAccumulatedSequence();
            parameters.spacingAfterLeftParenthesisExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        }
        // Read the member identifier or symbol
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.LeftSquareBracket) {
            parameters.memberSymbol = this._parseMemberSymbol(tokenReader, nodeForErrorContext);
            if (!parameters.memberSymbol) {
                return undefined;
            }
        }
        else {
            parameters.memberIdentifier = this._parseMemberIdentifier(tokenReader, tokenSequenceForErrorContext, nodeForErrorContext);
            if (!parameters.memberIdentifier) {
                return undefined;
            }
        }
        parameters.spacingAfterMemberExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Read the colon
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.Colon) {
            tokenReader.readToken();
            parameters.colonExcerpt = tokenReader.extractAccumulatedSequence();
            parameters.spacingAfterColonExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
            if (!parameters.leftParenthesisExcerpt) {
                // In the current TSDoc draft standard, a member reference with a selector requires the parentheses.
                // It would be reasonable to make the parentheses optional, and we are contemplating simplifying the
                // notation in the future.  But for now the parentheses are required.
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceSelectorMissingParens, 'Syntax error in declaration reference: the member selector must be enclosed in parentheses', parameters.colonExcerpt, nodeForErrorContext);
                return undefined;
            }
            // If there is a colon, then read the selector
            parameters.selector = this._parseMemberSelector(tokenReader, parameters.colonExcerpt, nodeForErrorContext);
            if (!parameters.selector) {
                return undefined;
            }
            parameters.spacingAfterSelectorExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        }
        else {
            if (parameters.leftParenthesisExcerpt) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingColon, 'Expecting a colon after the identifier because the expression is in parentheses', parameters.leftParenthesisExcerpt, nodeForErrorContext);
                return undefined;
            }
        }
        // Read the right parenthesis
        if (parameters.leftParenthesisExcerpt) {
            if (tokenReader.peekTokenKind() !== Token_1.TokenKind.RightParenthesis) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingRightParen, 'Expecting a matching right parenthesis', parameters.leftParenthesisExcerpt, nodeForErrorContext);
                return undefined;
            }
            tokenReader.readToken();
            parameters.rightParenthesisExcerpt = tokenReader.extractAccumulatedSequence();
            parameters.spacingAfterRightParenthesisExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        }
        return new nodes_1.DocMemberReference(parameters);
    }
    _parseMemberSymbol(tokenReader, nodeForErrorContext) {
        // Read the "["
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.LeftSquareBracket) {
            // This should be impossible since the caller ensures that peekTokenKind() === TokenKind.LeftSquareBracket
            throw new Error('Expecting "["');
        }
        tokenReader.readToken();
        const leftBracketExcerpt = tokenReader.extractAccumulatedSequence();
        const spacingAfterLeftBracketExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Read the declaration reference
        const declarationReference = this._parseDeclarationReference(tokenReader, leftBracketExcerpt, nodeForErrorContext);
        if (!declarationReference) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceSymbolSyntax, 'Missing declaration reference in symbol reference', leftBracketExcerpt, nodeForErrorContext);
            return undefined;
        }
        // (We don't need to worry about spacing here since _parseDeclarationReference() absorbs trailing spaces)
        // Read the "]"
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.RightSquareBracket) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingRightBracket, 'Missing closing square bracket for symbol reference', leftBracketExcerpt, nodeForErrorContext);
            return undefined;
        }
        tokenReader.readToken();
        const rightBracketExcerpt = tokenReader.extractAccumulatedSequence();
        return new nodes_1.DocMemberSymbol({
            parsed: true,
            configuration: this._configuration,
            leftBracketExcerpt,
            spacingAfterLeftBracketExcerpt,
            symbolReference: declarationReference,
            rightBracketExcerpt
        });
    }
    _parseMemberIdentifier(tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) {
        let leftQuoteExcerpt = undefined;
        let rightQuoteExcerpt = undefined;
        // Is this a quoted identifier?
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.DoubleQuote) {
            // Read the opening '"'
            tokenReader.readToken();
            leftQuoteExcerpt = tokenReader.extractAccumulatedSequence();
            // Read the text inside the quotes
            while (tokenReader.peekTokenKind() !== Token_1.TokenKind.DoubleQuote) {
                if (tokenReader.peekTokenKind() === Token_1.TokenKind.EndOfInput) {
                    this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingQuote, 'Unexpected end of input inside quoted member identifier', leftQuoteExcerpt, nodeForErrorContext);
                    return undefined;
                }
                tokenReader.readToken();
            }
            if (tokenReader.isAccumulatedSequenceEmpty()) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceEmptyIdentifier, 'The quoted identifier cannot be empty', leftQuoteExcerpt, nodeForErrorContext);
                return undefined;
            }
            const identifierExcerpt = tokenReader.extractAccumulatedSequence();
            // Read the closing '""
            tokenReader.readToken(); // read the quote
            rightQuoteExcerpt = tokenReader.extractAccumulatedSequence();
            return new nodes_1.DocMemberIdentifier({
                parsed: true,
                configuration: this._configuration,
                leftQuoteExcerpt,
                identifierExcerpt,
                rightQuoteExcerpt
            });
        }
        else {
            // Otherwise assume it's a valid TypeScript identifier
            let done = false;
            while (!done) {
                switch (tokenReader.peekTokenKind()) {
                    case Token_1.TokenKind.AsciiWord:
                    case Token_1.TokenKind.DollarSign:
                        tokenReader.readToken();
                        break;
                    default:
                        done = true;
                        break;
                }
            }
            if (tokenReader.isAccumulatedSequenceEmpty()) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingIdentifier, 'Syntax error in declaration reference: expecting a member identifier', tokenSequenceForErrorContext, nodeForErrorContext);
                return undefined;
            }
            const identifierExcerpt = tokenReader.extractAccumulatedSequence();
            const identifier = identifierExcerpt.toString();
            const explanation = StringChecks_1.StringChecks.explainIfInvalidUnquotedMemberIdentifier(identifier);
            if (explanation) {
                this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceUnquotedIdentifier, explanation, identifierExcerpt, nodeForErrorContext);
                return undefined;
            }
            return new nodes_1.DocMemberIdentifier({
                parsed: true,
                configuration: this._configuration,
                leftQuoteExcerpt,
                identifierExcerpt,
                rightQuoteExcerpt
            });
        }
    }
    _parseMemberSelector(tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) {
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.AsciiWord) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceMissingLabel, 'Expecting a selector label after the colon', tokenSequenceForErrorContext, nodeForErrorContext);
        }
        const selector = tokenReader.readToken().toString();
        const selectorExcerpt = tokenReader.extractAccumulatedSequence();
        const docMemberSelector = new nodes_1.DocMemberSelector({
            parsed: true,
            configuration: this._configuration,
            selectorExcerpt,
            selector
        });
        if (docMemberSelector.errorMessage) {
            this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceSelectorSyntax, docMemberSelector.errorMessage, selectorExcerpt, nodeForErrorContext);
            return undefined;
        }
        return docMemberSelector;
    }
    _parseHtmlStartTag(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const marker = tokenReader.createMarker();
        // Read the "<" delimiter
        const lessThanToken = tokenReader.readToken();
        if (lessThanToken.kind !== Token_1.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 = tokenReader.extractAccumulatedSequence();
        // Read the element name
        const nameExcerpt = this._parseHtmlName(tokenReader);
        if (isFailure(nameExcerpt)) {
            return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'Invalid HTML element: ', nameExcerpt);
        }
        const spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        const htmlAttributes = [];
        // Read the attributes until we see a ">" or "/>"
        while (tokenReader.peekTokenKind() === Token_1.TokenKind.AsciiWord) {
            // Read the attribute
            const attributeNode = 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 = tokenReader.createMarker();
        let selfClosingTag = false;
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.Slash) {
            tokenReader.readToken();
            selfClosingTag = true;
        }
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.GreaterThan) {
            const failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId_1.TSDocMessageId.HtmlTagMissingGreaterThan, 'Expecting an attribute or ">" or "/>"', endDelimiterMarker);
            return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'The HTML tag has invalid syntax: ', failure);
        }
        tokenReader.readToken();
        const closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
        // NOTE: We don't read excerptParameters.separator here, since if there is any it
        // will be represented as DocPlainText.
        return new nodes_1.DocHtmlStartTag({
            parsed: true,
            configuration: this._configuration,
            openingDelimiterExcerpt,
            nameExcerpt,
            spacingAfterNameExcerpt,
            htmlAttributes,
            selfClosingTag,
            closingDelimiterExcerpt
        });
    }
    _parseHtmlAttribute(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        // Read the attribute name
        const nameExcerpt = this._parseHtmlName(tokenReader);
        if (isFailure(nameExcerpt)) {
            return nameExcerpt;
        }
        const spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Read the equals
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Equals) {
            return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.HtmlTagMissingEquals, 'Expecting "=" after HTML attribute name');
        }
        tokenReader.readToken();
        const equalsExcerpt = tokenReader.extractAccumulatedSequence();
        const spacingAfterEqualsExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Read the attribute value
        const attributeValue = this._parseHtmlString(tokenReader);
        if (isFailure(attributeValue)) {
            return attributeValue;
        }
        const valueExcerpt = tokenReader.extractAccumulatedSequence();
        const spacingAfterValueExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        return new nodes_1.DocHtmlAttribute({
            parsed: true,
            configuration: this._configuration,
            nameExcerpt,
            spacingAfterNameExcerpt,
            equalsExcerpt,
            spacingAfterEqualsExcerpt,
            valueExcerpt,
            spacingAfterValueExcerpt
        });
    }
    _parseHtmlString(tokenReader) {
        const marker = tokenReader.createMarker();
        const quoteTokenKind = tokenReader.peekTokenKind();
        if (quoteTokenKind !== Token_1.TokenKind.DoubleQuote && quoteTokenKind !== Token_1.TokenKind.SingleQuote) {
            return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.HtmlTagMissingString, 'Expecting an HTML string starting with a single-quote or double-quote character');
        }
        tokenReader.readToken();
        let textWithoutQuotes = '';
        for (;;) {
            const peekedTokenKind = tokenReader.peekTokenKind();
            // Did we find the matching token?
            if (peekedTokenKind === quoteTokenKind) {
                tokenReader.readToken(); // extract the quote
                break;
            }
            if (peekedTokenKind === Token_1.TokenKind.EndOfInput || peekedTokenKind === Token_1.TokenKind.Newline) {
                return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.HtmlStringMissingQuote, 'The HTML string is missing its closing quote', marker);
            }
            textWithoutQuotes += tokenReader.readToken().toString();
        }
        // The next attribute cannot start immediately after this one
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.AsciiWord) {
            return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.TextAfterHtmlString, 'The next character after a closing quote must be spacing or punctuation');
        }
        return textWithoutQuotes;
    }
    _parseHtmlEndTag(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const marker = tokenReader.createMarker();
        // Read the "</" delimiter
        const lessThanToken = tokenReader.peekToken();
        if (lessThanToken.kind !== Token_1.TokenKind.LessThan) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.MissingHtmlEndTag, 'Expecting an HTML tag starting with "</"');
        }
        tokenReader.readToken();
        const slashToken = tokenReader.peekToken();
        if (slashToken.kind !== Token_1.TokenKind.Slash) {
            return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.MissingHtmlEndTag, 'Expecting an HTML tag starting with "</"');
        }
        tokenReader.readToken();
        // NOTE: Spaces are not permitted here
        // https://www.w3.org/TR/html5/syntax.html#end-tags
        const openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
        // Read the tag name
        const nameExcerpt = this._parseHtmlName(tokenReader);
        if (isFailure(nameExcerpt)) {
            return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'Expecting an HTML element name: ', nameExcerpt);
        }
        const spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
        // Read the closing ">"
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.GreaterThan) {
            const failure = this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.HtmlTagMissingGreaterThan, 'Expecting a closing ">" for the HTML tag');
            return this._backtrackAndCreateErrorForFailure(tokenReader, marker, '', failure);
        }
        tokenReader.readToken();
        const closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
        return new nodes_1.DocHtmlEndTag({
            parsed: true,
            configuration: this._configuration,
            openingDelimiterExcerpt,
            nameExcerpt,
            spacingAfterNameExcerpt,
            closingDelimiterExcerpt
        });
    }
    /**
     * Parses an HTML name such as an element name or attribute name.
     */
    _parseHtmlName(tokenReader) {
        const marker = tokenReader.createMarker();
        if (tokenReader.peekTokenKind() === Token_1.TokenKind.Spacing) {
            return this._createFailureForTokensSince(tokenReader, TSDocMessageId_1.TSDocMessageId.MalformedHtmlName, 'A space is not allowed here', marker);
        }
        let done = false;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.Hyphen:
                case Token_1.TokenKind.Period:
                case Token_1.TokenKind.AsciiWord:
                    tokenReader.readToken();
                    break;
                default:
                    done = true;
                    break;
            }
        }
        const excerpt = tokenReader.tryExtractAccumulatedSequence();
        if (!excerpt) {
            return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.MalformedHtmlName, 'Expecting an HTML name');
        }
        const htmlName = excerpt.toString();
        const explanation = StringChecks_1.StringChecks.explainIfInvalidHtmlName(htmlName);
        if (explanation) {
            return this._createFailureForTokensSince(tokenReader, TSDocMessageId_1.TSDocMessageId.MalformedHtmlName, explanation, marker);
        }
        if (this._configuration.validation.reportUnsupportedHtmlElements &&
            !this._configuration.isHtmlElementSupported(htmlName)) {
            return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.UnsupportedHtmlElementName, `The HTML element name ${JSON.stringify(htmlName)} is not defined by your TSDoc configuration`, marker);
        }
        return excerpt;
    }
    _parseFencedCode(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const startMarker = tokenReader.createMarker();
        const endOfOpeningDelimiterMarker = startMarker + 2;
        switch (tokenReader.peekPreviousTokenKind()) {
            case Token_1.TokenKind.Newline:
            case Token_1.TokenKind.EndOfInput:
                break;
            default:
                return this._backtrackAndCreateErrorRange(tokenReader, startMarker, 
                // include the three backticks so they don't get reinterpreted as a code span
                endOfOpeningDelimiterMarker, TSDocMessageId_1.TSDocMessageId.CodeFenceOpeningIndent, 'The opening backtick for a code fence must appear at the start of the line');
        }
        // Read the opening ``` delimiter
        let openingDelimiter = '';
        openingDelimiter += tokenReader.readToken();
        openingDelimiter += tokenReader.readToken();
        openingDelimiter += tokenReader.readToken();
        if (openingDelimiter !== '```') {
            // This would be a parser bug -- the caller of _parseFencedCode() should have verified this while
            // looking ahead to distinguish code spans/fences
            throw new Error('Expecting three backticks');
        }
        const openingFenceExcerpt = tokenReader.extractAccumulatedSequence();
        // Read any spaces after the delimiter,
        // but NOT the Newline since that goes with the spacingAfterLanguageExcerpt
        while (tokenReader.peekTokenKind() === Token_1.TokenKind.Spacing) {
            tokenReader.readToken();
        }
        const spacingAfterOpeningFenceExcerpt = tokenReader.tryExtractAccumulatedSequence();
        // Read the language specifier (if present) and newline
        let done = false;
        let startOfPaddingMarker = undefined;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.Spacing:
                case Token_1.TokenKind.Newline:
                    if (startOfPaddingMarker === undefined) {
                        // Starting a new run of spacing characters
                        startOfPaddingMarker = tokenReader.createMarker();
                    }
                    if (tokenReader.peekTokenKind() === Token_1.TokenKind.Newline) {
                        done = true;
                    }
                    tokenReader.readToken();
                    break;
                case Token_1.TokenKind.Backtick:
                    const failure = this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.CodeFenceSpecifierSyntax, 'The language specifier cannot contain backtick characters');
                    return this._backtrackAndCreateErrorRangeForFailure(tokenReader, startMarker, endOfOpeningDelimiterMarker, 'Error parsing code fence: ', failure);
                case Token_1.TokenKind.EndOfInput:
                    const failure2 = this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.CodeFenceMissingDelimiter, 'Missing closing delimiter');
                    return this._backtrackAndCreateErrorRangeForFailure(tokenReader, startMarker, endOfOpeningDelimiterMarker, 'Error parsing code fence: ', failure2);
                default:
                    // more non-spacing content
                    startOfPaddingMarker = undefined;
                    tokenReader.readToken();
                    break;
            }
        }
        // At this point, we must have accumulated at least a newline token.
        // Example: "pov-ray sdl    \n"
        const restOfLineExcerpt = tokenReader.extractAccumulatedSequence();
        // Example: "pov-ray sdl"
        const languageExcerpt = restOfLineExcerpt.getNewSequence(restOfLineExcerpt.startIndex, startOfPaddingMarker);
        // Example: "    \n"
        const spacingAfterLanguageExcerpt = restOfLineExcerpt.getNewSequence(startOfPaddingMarker, restOfLineExcerpt.endIndex);
        // Read the code content until we see the closing ``` delimiter
        let codeEndMarker = -1;
        let closingFenceStartMarker = -1;
        done = false;
        let tokenBeforeDelimiter;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.EndOfInput:
                    const failure2 = this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.CodeFenceMissingDelimiter, 'Missing closing delimiter');
                    return this._backtrackAndCreateErrorRangeForFailure(tokenReader, startMarker, endOfOpeningDelimiterMarker, 'Error parsing code fence: ', failure2);
                case Token_1.TokenKind.Newline:
                    tokenBeforeDelimiter = tokenReader.readToken();
                    codeEndMarker = tokenReader.createMarker();
                    while (tokenReader.peekTokenKind() === Token_1.TokenKind.Spacing) {
                        tokenBeforeDelimiter = tokenReader.readToken();
                    }
                    if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Backtick) {
                        break;
                    }
                    closingFenceStartMarker = tokenReader.createMarker();
                    tokenReader.readToken(); // first backtick
                    if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Backtick) {
                        break;
                    }
                    tokenReader.readToken(); // second backtick
                    if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Backtick) {
                        break;
                    }
                    tokenReader.readToken(); // third backtick
                    done = true;
                    break;
                default:
                    tokenReader.readToken();
                    break;
            }
        }
        if (tokenBeforeDelimiter.kind !== Token_1.TokenKind.Newline) {
            this._parserContext.log.addMessageForTextRange(TSDocMessageId_1.TSDocMessageId.CodeFenceClosingIndent, 'The closing delimiter for a code fence must not be indented', tokenBeforeDelimiter.range);
        }
        // Example: "code 1\ncode 2\n  ```"
        const codeAndDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
        // Example: "code 1\ncode 2\n"
        const codeExcerpt = codeAndDelimiterExcerpt.getNewSequence(codeAndDelimiterExcerpt.startIndex, codeEndMarker);
        // Example: "  "
        const spacingBeforeClosingFenceExcerpt = codeAndDelimiterExcerpt.getNewSequence(codeEndMarker, closingFenceStartMarker);
        // Example: "```"
        const closingFenceExcerpt = codeAndDelimiterExcerpt.getNewSequence(closingFenceStartMarker, codeAndDelimiterExcerpt.endIndex);
        // Read the spacing and newline after the closing delimiter
        done = false;
        while (!done) {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.Spacing:
                    tokenReader.readToken();
                    break;
                case Token_1.TokenKind.Newline:
                    done = true;
                    tokenReader.readToken();
                    break;
                case Token_1.TokenKind.EndOfInput:
                    done = true;
                    break;
                default:
                    this._parserContext.log.addMessageForTextRange(TSDocMessageId_1.TSDocMessageId.CodeFenceClosingSyntax, 'Unexpected characters after closing delimiter for code fence', tokenReader.peekToken().range);
                    done = true;
                    break;
            }
        }
        // Example: "   \n"
        const spacingAfterClosingFenceExcerpt = tokenReader.tryExtractAccumulatedSequence();
        return new nodes_1.DocFencedCode({
            parsed: true,
            configuration: this._configuration,
            openingFenceExcerpt,
            spacingAfterOpeningFenceExcerpt,
            languageExcerpt,
            spacingAfterLanguageExcerpt,
            codeExcerpt,
            spacingBeforeClosingFenceExcerpt,
            closingFenceExcerpt,
            spacingAfterClosingFenceExcerpt
        });
    }
    _parseCodeSpan(tokenReader) {
        tokenReader.assertAccumulatedSequenceIsEmpty();
        const marker = tokenReader.createMarker();
        // Parse the opening backtick
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.Backtick) {
            // This would be a parser bug -- the caller of _parseCodeSpan() should have verified this while
            // looking ahead to distinguish code spans/fences
            throw new Error('Expecting a code span starting with a backtick character "`"');
        }
        tokenReader.readToken(); // read the backtick
        const openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
        let codeExcerpt = undefined;
        let closingDelimiterExcerpt = undefined;
        // Parse the content backtick
        for (;;) {
            const peekedTokenKind = tokenReader.peekTokenKind();
            // Did we find the matching token?
            if (peekedTokenKind === Token_1.TokenKind.Backtick) {
                if (tokenReader.isAccumulatedSequenceEmpty()) {
                    return this._backtrackAndCreateErrorRange(tokenReader, marker, marker + 1, TSDocMessageId_1.TSDocMessageId.CodeSpanEmpty, 'A code span must contain at least one character between the backticks');
                }
                codeExcerpt = tokenReader.extractAccumulatedSequence();
                tokenReader.readToken();
                closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
                break;
            }
            if (peekedTokenKind === Token_1.TokenKind.EndOfInput || peekedTokenKind === Token_1.TokenKind.Newline) {
                return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId_1.TSDocMessageId.CodeSpanMissingDelimiter, 'The code span is missing its closing backtick');
            }
            tokenReader.readToken();
        }
        return new nodes_1.DocCodeSpan({
            parsed: true,
            configuration: this._configuration,
            openingDelimiterExcerpt,
            codeExcerpt,
            closingDelimiterExcerpt
        });
    }
    _tryReadSpacingAndNewlines(tokenReader) {
        let done = false;
        do {
            switch (tokenReader.peekTokenKind()) {
                case Token_1.TokenKind.Spacing:
                case Token_1.TokenKind.Newline:
                    tokenReader.readToken();
                    break;
                default:
                    done = true;
                    break;
            }
        } while (!done);
        return tokenReader.tryExtractAccumulatedSequence();
    }
    /**
     * Read the next token, and report it as a DocErrorText node.
     */
    _createError(tokenReader, messageId, errorMessage) {
        tokenReader.readToken();
        const textExcerpt = tokenReader.extractAccumulatedSequence();
        const docErrorText = new nodes_1.DocErrorText({
            parsed: true,
            configuration: this._configuration,
            textExcerpt,
            messageId,
            errorMessage,
            errorLocation: textExcerpt
        });
        this._parserContext.log.addMessageForDocErrorText(docErrorText);
        return docErrorText;
    }
    /**
     * Rewind to the specified marker, read the next token, and report it as a DocErrorText node.
     */
    _backtrackAndCreateError(tokenReader, marker, messageId, errorMessage) {
        tokenReader.backtrackToMarker(marker);
        return this._createError(tokenReader, messageId, errorMessage);
    }
    /**
     * Rewind to the errorStartMarker, read the tokens up to and including errorInclusiveEndMarker,
     * and report it as a DocErrorText node.
     */
    _backtrackAndCreateErrorRange(tokenReader, errorStartMarker, errorInclusiveEndMarker, messageId, errorMessage) {
        tokenReader.backtrackToMarker(errorStartMarker);
        while (tokenReader.createMarker() !== errorInclusiveEndMarker) {
            tokenReader.readToken();
        }
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) {
            tokenReader.readToken();
        }
        const textExcerpt = tokenReader.extractAccumulatedSequence();
        const docErrorText = new nodes_1.DocErrorText({
            parsed: true,
            configuration: this._configuration,
            textExcerpt,
            messageId,
            errorMessage: errorMessage,
            errorLocation: textExcerpt
        });
        this._parserContext.log.addMessageForDocErrorText(docErrorText);
        return docErrorText;
    }
    /**
     * Rewind to the specified marker, read the next token, and report it as a DocErrorText node
     * whose location is based on an IFailure.
     */
    _backtrackAndCreateErrorForFailure(tokenReader, marker, errorMessagePrefix, failure) {
        tokenReader.backtrackToMarker(marker);
        tokenReader.readToken();
        const textExcerpt = tokenReader.extractAccumulatedSequence();
        const docErrorText = new nodes_1.DocErrorText({
            parsed: true,
            configuration: this._configuration,
            textExcerpt,
            messageId: failure.failureMessageId,
            errorMessage: errorMessagePrefix + failure.failureMessage,
            errorLocation: failure.failureLocation
        });
        this._parserContext.log.addMessageForDocErrorText(docErrorText);
        return docErrorText;
    }
    /**
     * Rewind to the errorStartMarker, read the tokens up to and including errorInclusiveEndMarker,
     * and report it as a DocErrorText node whose location is based on an IFailure.
     */
    _backtrackAndCreateErrorRangeForFailure(tokenReader, errorStartMarker, errorInclusiveEndMarker, errorMessagePrefix, failure) {
        tokenReader.backtrackToMarker(errorStartMarker);
        while (tokenReader.createMarker() !== errorInclusiveEndMarker) {
            tokenReader.readToken();
        }
        if (tokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) {
            tokenReader.readToken();
        }
        const textExcerpt = tokenReader.extractAccumulatedSequence();
        const docErrorText = new nodes_1.DocErrorText({
            parsed: true,
            configuration: this._configuration,
            textExcerpt,
            messageId: failure.failureMessageId,
            errorMessage: errorMessagePrefix + failure.failureMessage,
            errorLocation: failure.failureLocation
        });
        this._parserContext.log.addMessageForDocErrorText(docErrorText);
        return docErrorText;
    }
    /**
     * Creates an IFailure whose TokenSequence is a single token.  If a marker is not specified,
     * then it is the current token.
     */
    _createFailureForToken(tokenReader, failureMessageId, failureMessage, tokenMarker) {
        if (!tokenMarker) {
            tokenMarker = tokenReader.createMarker();
        }
        const tokenSequence = new TokenSequence_1.TokenSequence({
            parserContext: this._parserContext,
            startIndex: tokenMarker,
            endIndex: tokenMarker + 1
        });
        return {
            failureMessageId,
            failureMessage,
            failureLocation: tokenSequence
        };
    }
    /**
     * Creates an IFailure whose TokenSequence starts from the specified marker and
     * encompasses all tokens read since then.  If none were read, then the next token used.
     */
    _createFailureForTokensSince(tokenReader, failureMessageId, failureMessage, startMarker) {
        let endMarker = tokenReader.createMarker();
        if (endMarker < startMarker) {
            // This would be a parser bug
            throw new Error('Invalid startMarker');
        }
        if (endMarker === startMarker) {
            ++endMarker;
        }
        const tokenSequence = new TokenSequence_1.TokenSequence({
            parserContext: this._parserContext,
            startIndex: startMarker,
            endIndex: endMarker
        });
        return {
            failureMessageId,
            failureMessage,
            failureLocation: tokenSequence
        };
    }
}
exports.NodeParser = NodeParser;
//# sourceMappingURL=NodeParser.js.map