"use strict"; /* eslint-disable max-lines */ var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NodeParser = void 0; var Token_1 = require("./Token"); var Tokenizer_1 = require("./Tokenizer"); var nodes_1 = require("../nodes"); var TokenSequence_1 = require("./TokenSequence"); var TokenReader_1 = require("./TokenReader"); var StringChecks_1 = require("./StringChecks"); var TSDocTagDefinition_1 = require("../configuration/TSDocTagDefinition"); var StandardTags_1 = require("../details/StandardTags"); var PlainTextEmitter_1 = require("../emitters/PlainTextEmitter"); var TSDocMessageId_1 = require("./TSDocMessageId"); function isFailure(resultOrFailure) { return resultOrFailure !== undefined && Object.hasOwnProperty.call(resultOrFailure, 'failureMessage'); } /** * The main parser for TSDoc comments. */ var NodeParser = /** @class */ (function () { function NodeParser(parserContext) { this._parserContext = parserContext; this._configuration = parserContext.configuration; this._currentSection = parserContext.docComment.summarySection; } NodeParser.prototype.parse = function () { var tokenReader = new TokenReader_1.TokenReader(this._parserContext); var 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); var marker = tokenReader.createMarker(); var docNode = this._parseInlineTag(tokenReader); var 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 var 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 "" or "". 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(); }; NodeParser.prototype._performValidationChecks = function () { var 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); } } }; NodeParser.prototype._validateTagDefinition = function (tagDefinition, tagName, expectingInlineTag, tokenSequenceForErrorContext, nodeForErrorContext) { if (tagDefinition) { var 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); } } }; NodeParser.prototype._pushAccumulatedPlainText = function (tokenReader) { if (!tokenReader.isAccumulatedSequenceEmpty()) { this._pushNode(new nodes_1.DocPlainText({ parsed: true, configuration: this._configuration, textExcerpt: tokenReader.extractAccumulatedSequence() })); } }; NodeParser.prototype._parseAndPushBlock = function (tokenReader) { var docComment = this._parserContext.docComment; var configuration = this._parserContext.configuration; var modifierTagSet = docComment.modifierTagSet; var parsedBlockTag = this._parseBlockTag(tokenReader); if (parsedBlockTag.kind !== nodes_1.DocNodeKind.BlockTag) { this._pushNode(parsedBlockTag); return; } var docBlockTag = parsedBlockTag; // Do we have a definition for this tag? var 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) { var 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) { var docParamBlock = this._parseParamBlock(tokenReader, docBlockTag, StandardTags_1.StandardTags.typeParam.tagName); this._parserContext.docComment.typeParams.add(docParamBlock); this._currentSection = docParamBlock.content; return; } else { var 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); }; NodeParser.prototype._addBlockToDocComment = function (block) { var 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); } }; /** * 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. */ NodeParser.prototype._tryParseJSDocTypeOrValueRest = function (tokenReader, openKind, closeKind, startMarker) { var quoteKind; var openCount = 1; while (openCount > 0) { var 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`. */ NodeParser.prototype._tryParseUnsupportedJSDocType = function (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; } var startMarker = tokenReader.createMarker(); tokenReader.readToken(); // read the "{" var 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); var 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`. */ NodeParser.prototype._tryParseJSDocOptionalNameRest = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); if (tokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) { var startMarker = tokenReader.createMarker(); return this._tryParseJSDocTypeOrValueRest(tokenReader, Token_1.TokenKind.LeftSquareBracket, Token_1.TokenKind.RightSquareBracket, startMarker); } return undefined; }; NodeParser.prototype._parseParamBlock = function (tokenReader, docBlockTag, tagName) { var startMarker = tokenReader.createMarker(); var spacingBeforeParameterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader); // Skip past a JSDoc type (i.e., '@param {type} paramName') if found, and report a warning. var unsupportedJsdocTypeBeforeParameterNameExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName); // Parse opening of invalid JSDoc optional parameter name (e.g., '[') var unsupportedJsdocOptionalNameOpenBracketExcerpt; if (tokenReader.peekTokenKind() === Token_1.TokenKind.LeftSquareBracket) { tokenReader.readToken(); // read the "[" unsupportedJsdocOptionalNameOpenBracketExcerpt = tokenReader.extractAccumulatedSequence(); } var parameterName = ''; var 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; } } var explanation = StringChecks_1.StringChecks.explainIfInvalidUnquotedIdentifier(parameterName); if (explanation !== undefined) { tokenReader.backtrackToMarker(startMarker); var errorParamBlock = new nodes_1.DocParamBlock({ configuration: this._configuration, blockTag: docBlockTag, parameterName: '' }); var 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; } var parameterNameExcerpt = tokenReader.extractAccumulatedSequence(); // Parse closing of invalid JSDoc optional parameter name (e.g., ']', '=default]'). var unsupportedJsdocOptionalNameRestExcerpt; if (unsupportedJsdocOptionalNameOpenBracketExcerpt) { unsupportedJsdocOptionalNameRestExcerpt = this._tryParseJSDocOptionalNameRest(tokenReader); var 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); } var spacingAfterParameterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader); // Skip past a trailing JSDoc type (i.e., '@param paramName {type}') if found, and report a warning. var unsupportedJsdocTypeAfterParameterNameExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName); // TODO: Warn if there is no space before or after the hyphen var hyphenExcerpt; var spacingAfterHyphenExcerpt; var 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: spacingBeforeParameterNameExcerpt, unsupportedJsdocTypeBeforeParameterNameExcerpt: unsupportedJsdocTypeBeforeParameterNameExcerpt, unsupportedJsdocOptionalNameOpenBracketExcerpt: unsupportedJsdocOptionalNameOpenBracketExcerpt, parameterNameExcerpt: parameterNameExcerpt, parameterName: parameterName, unsupportedJsdocOptionalNameRestExcerpt: unsupportedJsdocOptionalNameRestExcerpt, spacingAfterParameterNameExcerpt: spacingAfterParameterNameExcerpt, unsupportedJsdocTypeAfterParameterNameExcerpt: unsupportedJsdocTypeAfterParameterNameExcerpt, hyphenExcerpt: hyphenExcerpt, spacingAfterHyphenExcerpt: spacingAfterHyphenExcerpt, unsupportedJsdocTypeAfterHyphenExcerpt: unsupportedJsdocTypeAfterHyphenExcerpt }); }; NodeParser.prototype._pushNode = function (docNode) { if (this._configuration.docNodeManager.isAllowedChild(nodes_1.DocNodeKind.Paragraph, docNode.kind)) { this._currentSection.appendNodeInParagraph(docNode); } else { this._currentSection.appendNode(docNode); } }; NodeParser.prototype._parseBackslashEscape = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var 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'); } var 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'); } var encodedTextExcerpt = tokenReader.extractAccumulatedSequence(); return new nodes_1.DocEscapedText({ parsed: true, configuration: this._configuration, escapeStyle: nodes_1.EscapeStyle.CommonMarkBackslash, encodedTextExcerpt: encodedTextExcerpt, decodedText: escapedToken.toString() }); }; NodeParser.prototype._parseBlockTag = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var 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 var 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'); } var 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: var 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)) { var 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: tagName, tagNameExcerpt: tokenReader.extractAccumulatedSequence() }); }; NodeParser.prototype._parseInlineTag = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var 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(); var 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. var 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 var tagName = tokenReader.readToken().toString(); while (tokenReader.peekTokenKind() === Token_1.TokenKind.AsciiWord) { tagName += tokenReader.readToken().toString(); } if (tagName === '@') { // This is an unusual case var 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)) { var 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); } var tagNameExcerpt = tokenReader.extractAccumulatedSequence(); var 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) { var badCharacter = tokenReader.peekToken().range.toString()[0]; var 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); } } var 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())) { var 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: { var 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; } } var tagContentExcerpt = tokenReader.tryExtractAccumulatedSequence(); // Read the right curly bracket tokenReader.readToken(); var closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence(); var docInlineTagParsedParameters = { parsed: true, configuration: this._configuration, openingDelimiterExcerpt: openingDelimiterExcerpt, tagNameExcerpt: tagNameExcerpt, tagName: tagName, spacingAfterTagNameExcerpt: spacingAfterTagNameExcerpt, tagContentExcerpt: tagContentExcerpt, closingDelimiterExcerpt: closingDelimiterExcerpt }; var tagNameWithUpperCase = tagName.toUpperCase(); // Create a new TokenReader that will reparse the tokens corresponding to the tagContent. var embeddedTokenReader = new TokenReader_1.TokenReader(this._parserContext, tagContentExcerpt ? tagContentExcerpt : TokenSequence_1.TokenSequence.createEmpty(this._parserContext)); var 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 var tagDefinition = this._parserContext.configuration.tryGetTagDefinitionWithUpperCase(tagNameWithUpperCase); this._validateTagDefinition(tagDefinition, tagName, /* expectingInlineTag */ true, tagNameExcerpt, docNode); return docNode; }; NodeParser.prototype._parseInheritDocTag = function (docInlineTagParsedParameters, embeddedTokenReader) { // If an error occurs, then return a generic DocInlineTag instead of DocInheritDocTag var errorTag = new nodes_1.DocInlineTag(docInlineTagParsedParameters); var parameters = __assign({}, 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); }; NodeParser.prototype._parseLinkTag = function (docInlineTagParsedParameters, embeddedTokenReader) { // If an error occurs, then return a generic DocInlineTag instead of DocInheritDocTag var errorTag = new nodes_1.DocInlineTag(docInlineTagParsedParameters); var parameters = __assign({}, 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 "" 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 "://"? var looksLikeUrl = embeddedTokenReader.peekTokenKind() === Token_1.TokenKind.Slash && embeddedTokenReader.peekTokenAfterKind() === Token_1.TokenKind.Slash; var marker = embeddedTokenReader.createMarker(); var 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; var 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: var 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(); } } var 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); }; NodeParser.prototype._parseLinkTagUrlDestination = function (embeddedTokenReader, parameters, tokenSequenceForErrorContext, nodeForErrorContext) { // Simply accumulate everything up to the next space. We won't try to implement a proper // URI parser here. var urlDestination = ''; var 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()'); } var urlDestinationExcerpt = embeddedTokenReader.extractAccumulatedSequence(); var 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; }; NodeParser.prototype._parseLinkTagCodeDestination = function (embeddedTokenReader, parameters, tokenSequenceForErrorContext, nodeForErrorContext) { parameters.codeDestination = this._parseDeclarationReference(embeddedTokenReader, tokenSequenceForErrorContext, nodeForErrorContext); return !!parameters.codeDestination; }; NodeParser.prototype._parseDeclarationReference = function (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. var marker = tokenReader.createMarker(); var 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. var lookingForImportCharacters = true; var sawImportCharacters = false; var 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); var packageNameExcerpt; var importPathExcerpt; var importHashExcerpt; var 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: var scopedPackageName = tokenReader.peekTokenKind() === Token_1.TokenKind.AtSign; var 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 var 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 var 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: var 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: var expectingDot = memberReferences.length > 0; var 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: packageNameExcerpt, importPathExcerpt: importPathExcerpt, importHashExcerpt: importHashExcerpt, spacingAfterImportHashExcerpt: spacingAfterImportHashExcerpt, memberReferences: memberReferences }); }; NodeParser.prototype._parseMemberReference = function (tokenReader, expectingDot, tokenSequenceForErrorContext, nodeForErrorContext) { var 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); }; NodeParser.prototype._parseMemberSymbol = function (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(); var leftBracketExcerpt = tokenReader.extractAccumulatedSequence(); var spacingAfterLeftBracketExcerpt = this._tryReadSpacingAndNewlines(tokenReader); // Read the declaration reference var 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(); var rightBracketExcerpt = tokenReader.extractAccumulatedSequence(); return new nodes_1.DocMemberSymbol({ parsed: true, configuration: this._configuration, leftBracketExcerpt: leftBracketExcerpt, spacingAfterLeftBracketExcerpt: spacingAfterLeftBracketExcerpt, symbolReference: declarationReference, rightBracketExcerpt: rightBracketExcerpt }); }; NodeParser.prototype._parseMemberIdentifier = function (tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) { var leftQuoteExcerpt = undefined; var 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; } var 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: leftQuoteExcerpt, identifierExcerpt: identifierExcerpt, rightQuoteExcerpt: rightQuoteExcerpt }); } else { // Otherwise assume it's a valid TypeScript identifier var 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; } var identifierExcerpt = tokenReader.extractAccumulatedSequence(); var identifier = identifierExcerpt.toString(); var 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: leftQuoteExcerpt, identifierExcerpt: identifierExcerpt, rightQuoteExcerpt: rightQuoteExcerpt }); } }; NodeParser.prototype._parseMemberSelector = function (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); } var selector = tokenReader.readToken().toString(); var selectorExcerpt = tokenReader.extractAccumulatedSequence(); var docMemberSelector = new nodes_1.DocMemberSelector({ parsed: true, configuration: this._configuration, selectorExcerpt: selectorExcerpt, selector: selector }); if (docMemberSelector.errorMessage) { this._parserContext.log.addMessageForTokenSequence(TSDocMessageId_1.TSDocMessageId.ReferenceSelectorSyntax, docMemberSelector.errorMessage, selectorExcerpt, nodeForErrorContext); return undefined; } return docMemberSelector; }; NodeParser.prototype._parseHtmlStartTag = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var marker = tokenReader.createMarker(); // Read the "<" delimiter var 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 "<" var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence(); // Read the element name var nameExcerpt = this._parseHtmlName(tokenReader); if (isFailure(nameExcerpt)) { return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'Invalid HTML element: ', nameExcerpt); } var spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader); var htmlAttributes = []; // Read the attributes until we see a ">" or "/>" while (tokenReader.peekTokenKind() === Token_1.TokenKind.AsciiWord) { // Read the attribute var 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(); var endDelimiterMarker = tokenReader.createMarker(); var selfClosingTag = false; if (tokenReader.peekTokenKind() === Token_1.TokenKind.Slash) { tokenReader.readToken(); selfClosingTag = true; } if (tokenReader.peekTokenKind() !== Token_1.TokenKind.GreaterThan) { var 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(); var 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: openingDelimiterExcerpt, nameExcerpt: nameExcerpt, spacingAfterNameExcerpt: spacingAfterNameExcerpt, htmlAttributes: htmlAttributes, selfClosingTag: selfClosingTag, closingDelimiterExcerpt: closingDelimiterExcerpt }); }; NodeParser.prototype._parseHtmlAttribute = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); // Read the attribute name var nameExcerpt = this._parseHtmlName(tokenReader); if (isFailure(nameExcerpt)) { return nameExcerpt; } var 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(); var equalsExcerpt = tokenReader.extractAccumulatedSequence(); var spacingAfterEqualsExcerpt = this._tryReadSpacingAndNewlines(tokenReader); // Read the attribute value var attributeValue = this._parseHtmlString(tokenReader); if (isFailure(attributeValue)) { return attributeValue; } var valueExcerpt = tokenReader.extractAccumulatedSequence(); var spacingAfterValueExcerpt = this._tryReadSpacingAndNewlines(tokenReader); return new nodes_1.DocHtmlAttribute({ parsed: true, configuration: this._configuration, nameExcerpt: nameExcerpt, spacingAfterNameExcerpt: spacingAfterNameExcerpt, equalsExcerpt: equalsExcerpt, spacingAfterEqualsExcerpt: spacingAfterEqualsExcerpt, valueExcerpt: valueExcerpt, spacingAfterValueExcerpt: spacingAfterValueExcerpt }); }; NodeParser.prototype._parseHtmlString = function (tokenReader) { var marker = tokenReader.createMarker(); var 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(); var textWithoutQuotes = ''; for (;;) { var 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; }; NodeParser.prototype._parseHtmlEndTag = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var marker = tokenReader.createMarker(); // Read the "" if (tokenReader.peekTokenKind() !== Token_1.TokenKind.GreaterThan) { var failure = this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.HtmlTagMissingGreaterThan, 'Expecting a closing ">" for the HTML tag'); return this._backtrackAndCreateErrorForFailure(tokenReader, marker, '', failure); } tokenReader.readToken(); var closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence(); return new nodes_1.DocHtmlEndTag({ parsed: true, configuration: this._configuration, openingDelimiterExcerpt: openingDelimiterExcerpt, nameExcerpt: nameExcerpt, spacingAfterNameExcerpt: spacingAfterNameExcerpt, closingDelimiterExcerpt: closingDelimiterExcerpt }); }; /** * Parses an HTML name such as an element name or attribute name. */ NodeParser.prototype._parseHtmlName = function (tokenReader) { var 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); } var 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; } } var excerpt = tokenReader.tryExtractAccumulatedSequence(); if (!excerpt) { return this._createFailureForToken(tokenReader, TSDocMessageId_1.TSDocMessageId.MalformedHtmlName, 'Expecting an HTML name'); } var htmlName = excerpt.toString(); var 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; }; NodeParser.prototype._parseFencedCode = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var startMarker = tokenReader.createMarker(); var 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 var 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'); } var 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(); } var spacingAfterOpeningFenceExcerpt = tokenReader.tryExtractAccumulatedSequence(); // Read the language specifier (if present) and newline var done = false; var 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: var 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: var 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" var restOfLineExcerpt = tokenReader.extractAccumulatedSequence(); // Example: "pov-ray sdl" var languageExcerpt = restOfLineExcerpt.getNewSequence(restOfLineExcerpt.startIndex, startOfPaddingMarker); // Example: " \n" var spacingAfterLanguageExcerpt = restOfLineExcerpt.getNewSequence(startOfPaddingMarker, restOfLineExcerpt.endIndex); // Read the code content until we see the closing ``` delimiter var codeEndMarker = -1; var closingFenceStartMarker = -1; done = false; var tokenBeforeDelimiter; while (!done) { switch (tokenReader.peekTokenKind()) { case Token_1.TokenKind.EndOfInput: var 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 ```" var codeAndDelimiterExcerpt = tokenReader.extractAccumulatedSequence(); // Example: "code 1\ncode 2\n" var codeExcerpt = codeAndDelimiterExcerpt.getNewSequence(codeAndDelimiterExcerpt.startIndex, codeEndMarker); // Example: " " var spacingBeforeClosingFenceExcerpt = codeAndDelimiterExcerpt.getNewSequence(codeEndMarker, closingFenceStartMarker); // Example: "```" var 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" var spacingAfterClosingFenceExcerpt = tokenReader.tryExtractAccumulatedSequence(); return new nodes_1.DocFencedCode({ parsed: true, configuration: this._configuration, openingFenceExcerpt: openingFenceExcerpt, spacingAfterOpeningFenceExcerpt: spacingAfterOpeningFenceExcerpt, languageExcerpt: languageExcerpt, spacingAfterLanguageExcerpt: spacingAfterLanguageExcerpt, codeExcerpt: codeExcerpt, spacingBeforeClosingFenceExcerpt: spacingBeforeClosingFenceExcerpt, closingFenceExcerpt: closingFenceExcerpt, spacingAfterClosingFenceExcerpt: spacingAfterClosingFenceExcerpt }); }; NodeParser.prototype._parseCodeSpan = function (tokenReader) { tokenReader.assertAccumulatedSequenceIsEmpty(); var 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 var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence(); var codeExcerpt = undefined; var closingDelimiterExcerpt = undefined; // Parse the content backtick for (;;) { var 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: openingDelimiterExcerpt, codeExcerpt: codeExcerpt, closingDelimiterExcerpt: closingDelimiterExcerpt }); }; NodeParser.prototype._tryReadSpacingAndNewlines = function (tokenReader) { var 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. */ NodeParser.prototype._createError = function (tokenReader, messageId, errorMessage) { tokenReader.readToken(); var textExcerpt = tokenReader.extractAccumulatedSequence(); var docErrorText = new nodes_1.DocErrorText({ parsed: true, configuration: this._configuration, textExcerpt: textExcerpt, messageId: 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. */ NodeParser.prototype._backtrackAndCreateError = function (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. */ NodeParser.prototype._backtrackAndCreateErrorRange = function (tokenReader, errorStartMarker, errorInclusiveEndMarker, messageId, errorMessage) { tokenReader.backtrackToMarker(errorStartMarker); while (tokenReader.createMarker() !== errorInclusiveEndMarker) { tokenReader.readToken(); } if (tokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) { tokenReader.readToken(); } var textExcerpt = tokenReader.extractAccumulatedSequence(); var docErrorText = new nodes_1.DocErrorText({ parsed: true, configuration: this._configuration, textExcerpt: textExcerpt, messageId: 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. */ NodeParser.prototype._backtrackAndCreateErrorForFailure = function (tokenReader, marker, errorMessagePrefix, failure) { tokenReader.backtrackToMarker(marker); tokenReader.readToken(); var textExcerpt = tokenReader.extractAccumulatedSequence(); var docErrorText = new nodes_1.DocErrorText({ parsed: true, configuration: this._configuration, textExcerpt: 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. */ NodeParser.prototype._backtrackAndCreateErrorRangeForFailure = function (tokenReader, errorStartMarker, errorInclusiveEndMarker, errorMessagePrefix, failure) { tokenReader.backtrackToMarker(errorStartMarker); while (tokenReader.createMarker() !== errorInclusiveEndMarker) { tokenReader.readToken(); } if (tokenReader.peekTokenKind() !== Token_1.TokenKind.EndOfInput) { tokenReader.readToken(); } var textExcerpt = tokenReader.extractAccumulatedSequence(); var docErrorText = new nodes_1.DocErrorText({ parsed: true, configuration: this._configuration, textExcerpt: 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. */ NodeParser.prototype._createFailureForToken = function (tokenReader, failureMessageId, failureMessage, tokenMarker) { if (!tokenMarker) { tokenMarker = tokenReader.createMarker(); } var tokenSequence = new TokenSequence_1.TokenSequence({ parserContext: this._parserContext, startIndex: tokenMarker, endIndex: tokenMarker + 1 }); return { failureMessageId: failureMessageId, failureMessage: 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. */ NodeParser.prototype._createFailureForTokensSince = function (tokenReader, failureMessageId, failureMessage, startMarker) { var endMarker = tokenReader.createMarker(); if (endMarker < startMarker) { // This would be a parser bug throw new Error('Invalid startMarker'); } if (endMarker === startMarker) { ++endMarker; } var tokenSequence = new TokenSequence_1.TokenSequence({ parserContext: this._parserContext, startIndex: startMarker, endIndex: endMarker }); return { failureMessageId: failureMessageId, failureMessage: failureMessage, failureLocation: tokenSequence }; }; return NodeParser; }()); exports.NodeParser = NodeParser; //# sourceMappingURL=NodeParser.js.map