utils/node_modules/@microsoft/tsdoc/lib-commonjs/parser/NodeParser.js

1670 lines
92 KiB
JavaScript
Raw Normal View History

2024-02-07 01:33:07 -05:00
"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 "<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();
};
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 "<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 "://"?
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 "</" delimiter
var 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();
var 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
var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
// Read the tag name
var nameExcerpt = this._parseHtmlName(tokenReader);
if (isFailure(nameExcerpt)) {
return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'Expecting an HTML element name: ', nameExcerpt);
}
var spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
// Read the closing ">"
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