in tsdoc/src/parser/NodeParser.ts [790:979]
private _parseInlineTag(tokenReader: TokenReader): DocNode {
tokenReader.assertAccumulatedSequenceIsEmpty();
const marker: number = tokenReader.createMarker();
if (tokenReader.peekTokenKind() !== TokenKind.LeftCurlyBracket) {
return this._backtrackAndCreateError(
tokenReader,
marker,
TSDocMessageId.MissingTag,
'Expecting a TSDoc tag starting with "{"'
);
}
tokenReader.readToken();
const openingDelimiterExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();
// For inline tags, if we handle errors by backtracking to the "{" token, then the main loop
// will then interpret the "@" as a block tag, which is almost certainly incorrect. So the
// DocErrorText needs to include both the "{" and "@" tokens.
// We will use _backtrackAndCreateErrorRangeForFailure() for that.
const atSignMarker: number = tokenReader.createMarker();
if (tokenReader.peekTokenKind() !== TokenKind.AtSign) {
return this._backtrackAndCreateError(
tokenReader,
marker,
TSDocMessageId.MalformedInlineTag,
'Expecting a TSDoc tag starting with "{@"'
);
}
// Include the "@" as part of the tagName
let tagName: string = tokenReader.readToken().toString();
while (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
tagName += tokenReader.readToken().toString();
}
if (tagName === '@') {
// This is an unusual case
const failure: IFailure = this._createFailureForTokensSince(
tokenReader,
TSDocMessageId.MalformedInlineTag,
'Expecting a TSDoc inline tag name after the "{@" characters',
atSignMarker
);
return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
}
if (StringChecks.explainIfInvalidTSDocTagName(tagName)) {
const failure: IFailure = this._createFailureForTokensSince(
tokenReader,
TSDocMessageId.MalformedTagName,
'A TSDoc tag name must start with a letter and contain only letters and numbers',
atSignMarker
);
return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
}
const tagNameExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();
const spacingAfterTagNameExcerpt: TokenSequence | undefined = this._tryReadSpacingAndNewlines(
tokenReader
);
if (spacingAfterTagNameExcerpt === undefined) {
// If there were no spaces at all, that's an error unless it's the degenerate "{@tag}" case
if (tokenReader.peekTokenKind() !== TokenKind.RightCurlyBracket) {
const badCharacter: string = tokenReader.peekToken().range.toString()[0];
const failure: IFailure = this._createFailureForToken(
tokenReader,
TSDocMessageId.CharactersAfterInlineTag,
`The character ${JSON.stringify(
badCharacter
)} cannot appear after the TSDoc tag name; expecting a space`
);
return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
}
}
let done: boolean = false;
while (!done) {
switch (tokenReader.peekTokenKind()) {
case TokenKind.EndOfInput:
return this._backtrackAndCreateErrorRange(
tokenReader,
marker,
atSignMarker,
TSDocMessageId.InlineTagMissingRightBrace,
'The TSDoc inline tag name is missing its closing "}"'
);
case TokenKind.Backslash:
// http://usejsdoc.org/about-block-inline-tags.html
// "If your tag's text includes a closing curly brace (}), you must escape it with
// a leading backslash (\)."
tokenReader.readToken(); // discard the backslash
// In CommonMark, a backslash is only allowed before a punctuation
// character. In all other contexts, the backslash is interpreted as a
// literal character.
if (!Tokenizer.isPunctuation(tokenReader.peekTokenKind())) {
const failure: IFailure = this._createFailureForToken(
tokenReader,
TSDocMessageId.UnnecessaryBackslash,
'A backslash can only be used to escape a punctuation character'
);
return this._backtrackAndCreateErrorRangeForFailure(
tokenReader,
marker,
atSignMarker,
'Error reading inline TSDoc tag: ',
failure
);
}
tokenReader.readToken();
break;
case TokenKind.LeftCurlyBracket: {
const failure: IFailure = this._createFailureForToken(
tokenReader,
TSDocMessageId.InlineTagUnescapedBrace,
'The "{" character must be escaped with a backslash when used inside a TSDoc inline tag'
);
return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
}
case TokenKind.RightCurlyBracket:
done = true;
break;
default:
tokenReader.readToken();
break;
}
}
const tagContentExcerpt: TokenSequence | undefined = tokenReader.tryExtractAccumulatedSequence();
// Read the right curly bracket
tokenReader.readToken();
const closingDelimiterExcerpt: TokenSequence = tokenReader.extractAccumulatedSequence();
const docInlineTagParsedParameters: IDocInlineTagParsedParameters = {
parsed: true,
configuration: this._configuration,
openingDelimiterExcerpt,
tagNameExcerpt,
tagName,
spacingAfterTagNameExcerpt,
tagContentExcerpt,
closingDelimiterExcerpt
};
const tagNameWithUpperCase: string = tagName.toUpperCase();
// Create a new TokenReader that will reparse the tokens corresponding to the tagContent.
const embeddedTokenReader: TokenReader = new TokenReader(
this._parserContext,
tagContentExcerpt ? tagContentExcerpt : TokenSequence.createEmpty(this._parserContext)
);
let docNode: DocNode;
switch (tagNameWithUpperCase) {
case StandardTags.inheritDoc.tagNameWithUpperCase:
docNode = this._parseInheritDocTag(docInlineTagParsedParameters, embeddedTokenReader);
break;
case StandardTags.link.tagNameWithUpperCase:
docNode = this._parseLinkTag(docInlineTagParsedParameters, embeddedTokenReader);
break;
default:
docNode = new DocInlineTag(docInlineTagParsedParameters);
}
// Validate the tag
const tagDefinition:
| TSDocTagDefinition
| undefined = this._parserContext.configuration.tryGetTagDefinitionWithUpperCase(tagNameWithUpperCase);
this._validateTagDefinition(
tagDefinition,
tagName,
/* expectingInlineTag */ true,
tagNameExcerpt,
docNode
);
return docNode;
}