UNPKG

91.6 kBJavaScriptView Raw
1/* eslint-disable max-lines */
2var __assign = (this && this.__assign) || function () {
3 __assign = Object.assign || function(t) {
4 for (var s, i = 1, n = arguments.length; i < n; i++) {
5 s = arguments[i];
6 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7 t[p] = s[p];
8 }
9 return t;
10 };
11 return __assign.apply(this, arguments);
12};
13import { TokenKind } from './Token';
14import { Tokenizer } from './Tokenizer';
15import { DocBlockTag, DocCodeSpan, DocErrorText, DocEscapedText, DocHtmlAttribute, DocHtmlEndTag, DocHtmlStartTag, DocInlineTag, DocPlainText, DocSoftBreak, EscapeStyle, DocBlock, DocNodeKind, DocParamBlock, DocFencedCode, DocLinkTag, DocMemberReference, DocDeclarationReference, DocMemberSymbol, DocMemberIdentifier, DocMemberSelector, DocInheritDocTag } from '../nodes';
16import { TokenSequence } from './TokenSequence';
17import { TokenReader } from './TokenReader';
18import { StringChecks } from './StringChecks';
19import { TSDocTagSyntaxKind } from '../configuration/TSDocTagDefinition';
20import { StandardTags } from '../details/StandardTags';
21import { PlainTextEmitter } from '../emitters/PlainTextEmitter';
22import { TSDocMessageId } from './TSDocMessageId';
23function isFailure(resultOrFailure) {
24 return resultOrFailure !== undefined && Object.hasOwnProperty.call(resultOrFailure, 'failureMessage');
25}
26/**
27 * The main parser for TSDoc comments.
28 */
29var NodeParser = /** @class */ (function () {
30 function NodeParser(parserContext) {
31 this._parserContext = parserContext;
32 this._configuration = parserContext.configuration;
33 this._currentSection = parserContext.docComment.summarySection;
34 }
35 NodeParser.prototype.parse = function () {
36 var tokenReader = new TokenReader(this._parserContext);
37 var done = false;
38 while (!done) {
39 // Extract the next token
40 switch (tokenReader.peekTokenKind()) {
41 case TokenKind.EndOfInput:
42 done = true;
43 break;
44 case TokenKind.Newline:
45 this._pushAccumulatedPlainText(tokenReader);
46 tokenReader.readToken();
47 this._pushNode(new DocSoftBreak({
48 parsed: true,
49 configuration: this._configuration,
50 softBreakExcerpt: tokenReader.extractAccumulatedSequence()
51 }));
52 break;
53 case TokenKind.Backslash:
54 this._pushAccumulatedPlainText(tokenReader);
55 this._pushNode(this._parseBackslashEscape(tokenReader));
56 break;
57 case TokenKind.AtSign:
58 this._pushAccumulatedPlainText(tokenReader);
59 this._parseAndPushBlock(tokenReader);
60 break;
61 case TokenKind.LeftCurlyBracket: {
62 this._pushAccumulatedPlainText(tokenReader);
63 var marker = tokenReader.createMarker();
64 var docNode = this._parseInlineTag(tokenReader);
65 var docComment = this._parserContext.docComment;
66 if (docNode instanceof DocInheritDocTag) {
67 // The @inheritDoc tag is irregular because it looks like an inline tag, but
68 // it actually represents the entire comment body
69 var tagEndMarker = tokenReader.createMarker() - 1;
70 if (docComment.inheritDocTag === undefined) {
71 this._parserContext.docComment.inheritDocTag = docNode;
72 }
73 else {
74 this._pushNode(this._backtrackAndCreateErrorRange(tokenReader, marker, tagEndMarker, TSDocMessageId.ExtraInheritDocTag, 'A doc comment cannot have more than one @inheritDoc tag'));
75 }
76 }
77 else {
78 this._pushNode(docNode);
79 }
80 break;
81 }
82 case TokenKind.RightCurlyBracket:
83 this._pushAccumulatedPlainText(tokenReader);
84 this._pushNode(this._createError(tokenReader, TSDocMessageId.EscapeRightBrace, 'The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag'));
85 break;
86 case TokenKind.LessThan:
87 this._pushAccumulatedPlainText(tokenReader);
88 // Look ahead two tokens to see if this is "<a>" or "</a>".
89 if (tokenReader.peekTokenAfterKind() === TokenKind.Slash) {
90 this._pushNode(this._parseHtmlEndTag(tokenReader));
91 }
92 else {
93 this._pushNode(this._parseHtmlStartTag(tokenReader));
94 }
95 break;
96 case TokenKind.GreaterThan:
97 this._pushAccumulatedPlainText(tokenReader);
98 this._pushNode(this._createError(tokenReader, TSDocMessageId.EscapeGreaterThan, 'The ">" character should be escaped using a backslash to avoid confusion with an HTML tag'));
99 break;
100 case TokenKind.Backtick:
101 this._pushAccumulatedPlainText(tokenReader);
102 if (tokenReader.peekTokenAfterKind() === TokenKind.Backtick &&
103 tokenReader.peekTokenAfterAfterKind() === TokenKind.Backtick) {
104 this._pushNode(this._parseFencedCode(tokenReader));
105 }
106 else {
107 this._pushNode(this._parseCodeSpan(tokenReader));
108 }
109 break;
110 default:
111 // If nobody recognized this token, then accumulate plain text
112 tokenReader.readToken();
113 break;
114 }
115 }
116 this._pushAccumulatedPlainText(tokenReader);
117 this._performValidationChecks();
118 };
119 NodeParser.prototype._performValidationChecks = function () {
120 var docComment = this._parserContext.docComment;
121 if (docComment.deprecatedBlock) {
122 if (!PlainTextEmitter.hasAnyTextContent(docComment.deprecatedBlock)) {
123 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.MissingDeprecationMessage, "The " + docComment.deprecatedBlock.blockTag.tagName + " block must include a deprecation message," +
124 " e.g. describing the recommended alternative", docComment.deprecatedBlock.blockTag.getTokenSequence(), docComment.deprecatedBlock);
125 }
126 }
127 if (docComment.inheritDocTag) {
128 if (docComment.remarksBlock) {
129 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.InheritDocIncompatibleTag, "A \"" + docComment.remarksBlock.blockTag.tagName + "\" block must not be used, because that" +
130 " content is provided by the @inheritDoc tag", docComment.remarksBlock.blockTag.getTokenSequence(), docComment.remarksBlock.blockTag);
131 }
132 if (PlainTextEmitter.hasAnyTextContent(docComment.summarySection)) {
133 this._parserContext.log.addMessageForTextRange(TSDocMessageId.InheritDocIncompatibleSummary, 'The summary section must not have any content, because that' +
134 ' content is provided by the @inheritDoc tag', this._parserContext.commentRange);
135 }
136 }
137 };
138 NodeParser.prototype._validateTagDefinition = function (tagDefinition, tagName, expectingInlineTag, tokenSequenceForErrorContext, nodeForErrorContext) {
139 if (tagDefinition) {
140 var isInlineTag = tagDefinition.syntaxKind === TSDocTagSyntaxKind.InlineTag;
141 if (isInlineTag !== expectingInlineTag) {
142 // The tag is defined, but it is used incorrectly
143 if (expectingInlineTag) {
144 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.TagShouldNotHaveBraces, "The TSDoc tag \"" + tagName + "\" is not an inline tag; it must not be enclosed in \"{ }\" braces", tokenSequenceForErrorContext, nodeForErrorContext);
145 }
146 else {
147 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.InlineTagMissingBraces, "The TSDoc tag \"" + tagName + "\" is an inline tag; it must be enclosed in \"{ }\" braces", tokenSequenceForErrorContext, nodeForErrorContext);
148 }
149 }
150 else {
151 if (this._parserContext.configuration.validation.reportUnsupportedTags) {
152 if (!this._parserContext.configuration.isTagSupported(tagDefinition)) {
153 // The tag is defined, but not supported
154 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.UnsupportedTag, "The TSDoc tag \"" + tagName + "\" is not supported by this tool", tokenSequenceForErrorContext, nodeForErrorContext);
155 }
156 }
157 }
158 }
159 else {
160 // The tag is not defined
161 if (!this._parserContext.configuration.validation.ignoreUndefinedTags) {
162 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.UndefinedTag, "The TSDoc tag \"" + tagName + "\" is not defined in this configuration", tokenSequenceForErrorContext, nodeForErrorContext);
163 }
164 }
165 };
166 NodeParser.prototype._pushAccumulatedPlainText = function (tokenReader) {
167 if (!tokenReader.isAccumulatedSequenceEmpty()) {
168 this._pushNode(new DocPlainText({
169 parsed: true,
170 configuration: this._configuration,
171 textExcerpt: tokenReader.extractAccumulatedSequence()
172 }));
173 }
174 };
175 NodeParser.prototype._parseAndPushBlock = function (tokenReader) {
176 var docComment = this._parserContext.docComment;
177 var configuration = this._parserContext.configuration;
178 var modifierTagSet = docComment.modifierTagSet;
179 var parsedBlockTag = this._parseBlockTag(tokenReader);
180 if (parsedBlockTag.kind !== DocNodeKind.BlockTag) {
181 this._pushNode(parsedBlockTag);
182 return;
183 }
184 var docBlockTag = parsedBlockTag;
185 // Do we have a definition for this tag?
186 var tagDefinition = configuration.tryGetTagDefinitionWithUpperCase(docBlockTag.tagNameWithUpperCase);
187 this._validateTagDefinition(tagDefinition, docBlockTag.tagName,
188 /* expectingInlineTag */ false, docBlockTag.getTokenSequence(), docBlockTag);
189 if (tagDefinition) {
190 switch (tagDefinition.syntaxKind) {
191 case TSDocTagSyntaxKind.BlockTag:
192 if (docBlockTag.tagNameWithUpperCase === StandardTags.param.tagNameWithUpperCase) {
193 var docParamBlock = this._parseParamBlock(tokenReader, docBlockTag, StandardTags.param.tagName);
194 this._parserContext.docComment.params.add(docParamBlock);
195 this._currentSection = docParamBlock.content;
196 return;
197 }
198 else if (docBlockTag.tagNameWithUpperCase === StandardTags.typeParam.tagNameWithUpperCase) {
199 var docParamBlock = this._parseParamBlock(tokenReader, docBlockTag, StandardTags.typeParam.tagName);
200 this._parserContext.docComment.typeParams.add(docParamBlock);
201 this._currentSection = docParamBlock.content;
202 return;
203 }
204 else {
205 var newBlock = new DocBlock({
206 configuration: this._configuration,
207 blockTag: docBlockTag
208 });
209 this._addBlockToDocComment(newBlock);
210 this._currentSection = newBlock.content;
211 }
212 return;
213 case TSDocTagSyntaxKind.ModifierTag:
214 // The block tag was recognized as a modifier, so add it to the modifier tag set
215 // and do NOT call currentSection.appendNode(parsedNode)
216 modifierTagSet.addTag(docBlockTag);
217 return;
218 }
219 }
220 this._pushNode(docBlockTag);
221 };
222 NodeParser.prototype._addBlockToDocComment = function (block) {
223 var docComment = this._parserContext.docComment;
224 switch (block.blockTag.tagNameWithUpperCase) {
225 case StandardTags.remarks.tagNameWithUpperCase:
226 docComment.remarksBlock = block;
227 break;
228 case StandardTags.privateRemarks.tagNameWithUpperCase:
229 docComment.privateRemarks = block;
230 break;
231 case StandardTags.deprecated.tagNameWithUpperCase:
232 docComment.deprecatedBlock = block;
233 break;
234 case StandardTags.returns.tagNameWithUpperCase:
235 docComment.returnsBlock = block;
236 break;
237 case StandardTags.see.tagNameWithUpperCase:
238 docComment._appendSeeBlock(block);
239 break;
240 default:
241 docComment.appendCustomBlock(block);
242 }
243 };
244 /**
245 * Used by `_parseParamBlock()`, this parses a JSDoc expression remainder like `string}` or `="]"]` from
246 * an input like `@param {string} [x="]"] - the X value`. It detects nested balanced pairs of delimiters
247 * and escaped string literals.
248 */
249 NodeParser.prototype._tryParseJSDocTypeOrValueRest = function (tokenReader, openKind, closeKind, startMarker) {
250 var quoteKind;
251 var openCount = 1;
252 while (openCount > 0) {
253 var tokenKind = tokenReader.peekTokenKind();
254 switch (tokenKind) {
255 case openKind:
256 // ignore open bracket/brace inside of a quoted string
257 if (quoteKind === undefined)
258 openCount++;
259 break;
260 case closeKind:
261 // ignore close bracket/brace inside of a quoted string
262 if (quoteKind === undefined)
263 openCount--;
264 break;
265 case TokenKind.Backslash:
266 // ignore backslash outside of quoted string
267 if (quoteKind !== undefined) {
268 // skip the backslash and the next character.
269 tokenReader.readToken();
270 tokenKind = tokenReader.peekTokenKind();
271 }
272 break;
273 case TokenKind.DoubleQuote:
274 case TokenKind.SingleQuote:
275 case TokenKind.Backtick:
276 if (quoteKind === tokenKind) {
277 // exit quoted string if quote character matches.
278 quoteKind = undefined;
279 }
280 else if (quoteKind === undefined) {
281 // start quoted string if not in a quoted string.
282 quoteKind = tokenKind;
283 }
284 break;
285 }
286 // give up at end of input and backtrack to start.
287 if (tokenKind === TokenKind.EndOfInput) {
288 tokenReader.backtrackToMarker(startMarker);
289 return undefined;
290 }
291 tokenReader.readToken();
292 }
293 return tokenReader.tryExtractAccumulatedSequence();
294 };
295 /**
296 * Used by `_parseParamBlock()`, this parses a JSDoc expression like `{string}` from
297 * an input like `@param {string} x - the X value`.
298 */
299 NodeParser.prototype._tryParseUnsupportedJSDocType = function (tokenReader, docBlockTag, tagName) {
300 tokenReader.assertAccumulatedSequenceIsEmpty();
301 // do not parse `{@...` as a JSDoc type
302 if (tokenReader.peekTokenKind() !== TokenKind.LeftCurlyBracket ||
303 tokenReader.peekTokenAfterKind() === TokenKind.AtSign) {
304 return undefined;
305 }
306 var startMarker = tokenReader.createMarker();
307 tokenReader.readToken(); // read the "{"
308 var jsdocTypeExcerpt = this._tryParseJSDocTypeOrValueRest(tokenReader, TokenKind.LeftCurlyBracket, TokenKind.RightCurlyBracket, startMarker);
309 if (jsdocTypeExcerpt) {
310 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ParamTagWithInvalidType, 'The ' + tagName + " block should not include a JSDoc-style '{type}'", jsdocTypeExcerpt, docBlockTag);
311 var spacingAfterJsdocTypeExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
312 if (spacingAfterJsdocTypeExcerpt) {
313 jsdocTypeExcerpt = jsdocTypeExcerpt.getNewSequence(jsdocTypeExcerpt.startIndex, spacingAfterJsdocTypeExcerpt.endIndex);
314 }
315 }
316 return jsdocTypeExcerpt;
317 };
318 /**
319 * Used by `_parseParamBlock()`, this parses a JSDoc expression remainder like `=[]]` from
320 * an input like `@param {string} [x=[]] - the X value`.
321 */
322 NodeParser.prototype._tryParseJSDocOptionalNameRest = function (tokenReader) {
323 tokenReader.assertAccumulatedSequenceIsEmpty();
324 if (tokenReader.peekTokenKind() !== TokenKind.EndOfInput) {
325 var startMarker = tokenReader.createMarker();
326 return this._tryParseJSDocTypeOrValueRest(tokenReader, TokenKind.LeftSquareBracket, TokenKind.RightSquareBracket, startMarker);
327 }
328 return undefined;
329 };
330 NodeParser.prototype._parseParamBlock = function (tokenReader, docBlockTag, tagName) {
331 var startMarker = tokenReader.createMarker();
332 var spacingBeforeParameterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
333 // Skip past a JSDoc type (i.e., '@param {type} paramName') if found, and report a warning.
334 var unsupportedJsdocTypeBeforeParameterNameExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName);
335 // Parse opening of invalid JSDoc optional parameter name (e.g., '[')
336 var unsupportedJsdocOptionalNameOpenBracketExcerpt;
337 if (tokenReader.peekTokenKind() === TokenKind.LeftSquareBracket) {
338 tokenReader.readToken(); // read the "["
339 unsupportedJsdocOptionalNameOpenBracketExcerpt = tokenReader.extractAccumulatedSequence();
340 }
341 var parameterName = '';
342 var done = false;
343 while (!done) {
344 switch (tokenReader.peekTokenKind()) {
345 case TokenKind.AsciiWord:
346 case TokenKind.Period:
347 case TokenKind.DollarSign:
348 parameterName += tokenReader.readToken();
349 break;
350 default:
351 done = true;
352 break;
353 }
354 }
355 var explanation = StringChecks.explainIfInvalidUnquotedIdentifier(parameterName);
356 if (explanation !== undefined) {
357 tokenReader.backtrackToMarker(startMarker);
358 var errorParamBlock = new DocParamBlock({
359 configuration: this._configuration,
360 blockTag: docBlockTag,
361 parameterName: ''
362 });
363 var errorMessage = parameterName.length > 0
364 ? 'The ' + tagName + ' block should be followed by a valid parameter name: ' + explanation
365 : 'The ' + tagName + ' block should be followed by a parameter name';
366 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ParamTagWithInvalidName, errorMessage, docBlockTag.getTokenSequence(), docBlockTag);
367 return errorParamBlock;
368 }
369 var parameterNameExcerpt = tokenReader.extractAccumulatedSequence();
370 // Parse closing of invalid JSDoc optional parameter name (e.g., ']', '=default]').
371 var unsupportedJsdocOptionalNameRestExcerpt;
372 if (unsupportedJsdocOptionalNameOpenBracketExcerpt) {
373 unsupportedJsdocOptionalNameRestExcerpt = this._tryParseJSDocOptionalNameRest(tokenReader);
374 var errorSequence = unsupportedJsdocOptionalNameOpenBracketExcerpt;
375 if (unsupportedJsdocOptionalNameRestExcerpt) {
376 errorSequence = docBlockTag
377 .getTokenSequence()
378 .getNewSequence(unsupportedJsdocOptionalNameOpenBracketExcerpt.startIndex, unsupportedJsdocOptionalNameRestExcerpt.endIndex);
379 }
380 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ParamTagWithInvalidOptionalName, 'The ' +
381 tagName +
382 " should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.", errorSequence, docBlockTag);
383 }
384 var spacingAfterParameterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
385 // Skip past a trailing JSDoc type (i.e., '@param paramName {type}') if found, and report a warning.
386 var unsupportedJsdocTypeAfterParameterNameExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName);
387 // TODO: Warn if there is no space before or after the hyphen
388 var hyphenExcerpt;
389 var spacingAfterHyphenExcerpt;
390 var unsupportedJsdocTypeAfterHyphenExcerpt;
391 if (tokenReader.peekTokenKind() === TokenKind.Hyphen) {
392 tokenReader.readToken();
393 hyphenExcerpt = tokenReader.extractAccumulatedSequence();
394 // TODO: Only read one space
395 spacingAfterHyphenExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
396 // Skip past a JSDoc type (i.e., '@param paramName - {type}') if found, and report a warning.
397 unsupportedJsdocTypeAfterHyphenExcerpt = this._tryParseUnsupportedJSDocType(tokenReader, docBlockTag, tagName);
398 }
399 else {
400 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ParamTagMissingHyphen, 'The ' + tagName + ' block should be followed by a parameter name and then a hyphen', docBlockTag.getTokenSequence(), docBlockTag);
401 }
402 return new DocParamBlock({
403 parsed: true,
404 configuration: this._configuration,
405 blockTag: docBlockTag,
406 spacingBeforeParameterNameExcerpt: spacingBeforeParameterNameExcerpt,
407 unsupportedJsdocTypeBeforeParameterNameExcerpt: unsupportedJsdocTypeBeforeParameterNameExcerpt,
408 unsupportedJsdocOptionalNameOpenBracketExcerpt: unsupportedJsdocOptionalNameOpenBracketExcerpt,
409 parameterNameExcerpt: parameterNameExcerpt,
410 parameterName: parameterName,
411 unsupportedJsdocOptionalNameRestExcerpt: unsupportedJsdocOptionalNameRestExcerpt,
412 spacingAfterParameterNameExcerpt: spacingAfterParameterNameExcerpt,
413 unsupportedJsdocTypeAfterParameterNameExcerpt: unsupportedJsdocTypeAfterParameterNameExcerpt,
414 hyphenExcerpt: hyphenExcerpt,
415 spacingAfterHyphenExcerpt: spacingAfterHyphenExcerpt,
416 unsupportedJsdocTypeAfterHyphenExcerpt: unsupportedJsdocTypeAfterHyphenExcerpt
417 });
418 };
419 NodeParser.prototype._pushNode = function (docNode) {
420 if (this._configuration.docNodeManager.isAllowedChild(DocNodeKind.Paragraph, docNode.kind)) {
421 this._currentSection.appendNodeInParagraph(docNode);
422 }
423 else {
424 this._currentSection.appendNode(docNode);
425 }
426 };
427 NodeParser.prototype._parseBackslashEscape = function (tokenReader) {
428 tokenReader.assertAccumulatedSequenceIsEmpty();
429 var marker = tokenReader.createMarker();
430 tokenReader.readToken(); // read the backslash
431 if (tokenReader.peekTokenKind() === TokenKind.EndOfInput) {
432 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.UnnecessaryBackslash, 'A backslash must precede another character that is being escaped');
433 }
434 var escapedToken = tokenReader.readToken(); // read the escaped character
435 // In CommonMark, a backslash is only allowed before a punctuation
436 // character. In all other contexts, the backslash is interpreted as a
437 // literal character.
438 if (!Tokenizer.isPunctuation(escapedToken.kind)) {
439 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.UnnecessaryBackslash, 'A backslash can only be used to escape a punctuation character');
440 }
441 var encodedTextExcerpt = tokenReader.extractAccumulatedSequence();
442 return new DocEscapedText({
443 parsed: true,
444 configuration: this._configuration,
445 escapeStyle: EscapeStyle.CommonMarkBackslash,
446 encodedTextExcerpt: encodedTextExcerpt,
447 decodedText: escapedToken.toString()
448 });
449 };
450 NodeParser.prototype._parseBlockTag = function (tokenReader) {
451 tokenReader.assertAccumulatedSequenceIsEmpty();
452 var marker = tokenReader.createMarker();
453 if (tokenReader.peekTokenKind() !== TokenKind.AtSign) {
454 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.MissingTag, 'Expecting a TSDoc tag starting with "@"');
455 }
456 // "@one" is a valid TSDoc tag at the start of a line, but "@one@two" is
457 // a syntax error. For two tags it should be "@one @two", or for literal text it
458 // should be "\@one\@two".
459 switch (tokenReader.peekPreviousTokenKind()) {
460 case TokenKind.EndOfInput:
461 case TokenKind.Spacing:
462 case TokenKind.Newline:
463 break;
464 default:
465 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.AtSignInWord, 'The "@" character looks like part of a TSDoc tag; use a backslash to escape it');
466 }
467 // Include the "@" as part of the tagName
468 var tagName = tokenReader.readToken().toString();
469 if (tokenReader.peekTokenKind() !== TokenKind.AsciiWord) {
470 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.AtSignWithoutTagName, 'Expecting a TSDoc tag name after "@"; if it is not a tag, use a backslash to escape this character');
471 }
472 var tagNameMarker = tokenReader.createMarker();
473 while (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
474 tagName += tokenReader.readToken().toString();
475 }
476 switch (tokenReader.peekTokenKind()) {
477 case TokenKind.Spacing:
478 case TokenKind.Newline:
479 case TokenKind.EndOfInput:
480 break;
481 default:
482 var badCharacter = tokenReader.peekToken().range.toString()[0];
483 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.CharactersAfterBlockTag, "The token \"" + tagName + "\" looks like a TSDoc tag but contains an invalid character" +
484 (" " + JSON.stringify(badCharacter) + "; if it is not a tag, use a backslash to escape the \"@\""));
485 }
486 if (StringChecks.explainIfInvalidTSDocTagName(tagName)) {
487 var failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId.MalformedTagName, 'A TSDoc tag name must start with a letter and contain only letters and numbers', tagNameMarker);
488 return this._backtrackAndCreateErrorForFailure(tokenReader, marker, '', failure);
489 }
490 return new DocBlockTag({
491 parsed: true,
492 configuration: this._configuration,
493 tagName: tagName,
494 tagNameExcerpt: tokenReader.extractAccumulatedSequence()
495 });
496 };
497 NodeParser.prototype._parseInlineTag = function (tokenReader) {
498 tokenReader.assertAccumulatedSequenceIsEmpty();
499 var marker = tokenReader.createMarker();
500 if (tokenReader.peekTokenKind() !== TokenKind.LeftCurlyBracket) {
501 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.MissingTag, 'Expecting a TSDoc tag starting with "{"');
502 }
503 tokenReader.readToken();
504 var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
505 // For inline tags, if we handle errors by backtracking to the "{" token, then the main loop
506 // will then interpret the "@" as a block tag, which is almost certainly incorrect. So the
507 // DocErrorText needs to include both the "{" and "@" tokens.
508 // We will use _backtrackAndCreateErrorRangeForFailure() for that.
509 var atSignMarker = tokenReader.createMarker();
510 if (tokenReader.peekTokenKind() !== TokenKind.AtSign) {
511 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.MalformedInlineTag, 'Expecting a TSDoc tag starting with "{@"');
512 }
513 // Include the "@" as part of the tagName
514 var tagName = tokenReader.readToken().toString();
515 while (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
516 tagName += tokenReader.readToken().toString();
517 }
518 if (tagName === '@') {
519 // This is an unusual case
520 var failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId.MalformedInlineTag, 'Expecting a TSDoc inline tag name after the "{@" characters', atSignMarker);
521 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
522 }
523 if (StringChecks.explainIfInvalidTSDocTagName(tagName)) {
524 var failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId.MalformedTagName, 'A TSDoc tag name must start with a letter and contain only letters and numbers', atSignMarker);
525 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
526 }
527 var tagNameExcerpt = tokenReader.extractAccumulatedSequence();
528 var spacingAfterTagNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
529 if (spacingAfterTagNameExcerpt === undefined) {
530 // If there were no spaces at all, that's an error unless it's the degenerate "{@tag}" case
531 if (tokenReader.peekTokenKind() !== TokenKind.RightCurlyBracket) {
532 var badCharacter = tokenReader.peekToken().range.toString()[0];
533 var failure = this._createFailureForToken(tokenReader, TSDocMessageId.CharactersAfterInlineTag, "The character " + JSON.stringify(badCharacter) + " cannot appear after the TSDoc tag name; expecting a space");
534 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
535 }
536 }
537 var done = false;
538 while (!done) {
539 switch (tokenReader.peekTokenKind()) {
540 case TokenKind.EndOfInput:
541 return this._backtrackAndCreateErrorRange(tokenReader, marker, atSignMarker, TSDocMessageId.InlineTagMissingRightBrace, 'The TSDoc inline tag name is missing its closing "}"');
542 case TokenKind.Backslash:
543 // http://usejsdoc.org/about-block-inline-tags.html
544 // "If your tag's text includes a closing curly brace (}), you must escape it with
545 // a leading backslash (\)."
546 tokenReader.readToken(); // discard the backslash
547 // In CommonMark, a backslash is only allowed before a punctuation
548 // character. In all other contexts, the backslash is interpreted as a
549 // literal character.
550 if (!Tokenizer.isPunctuation(tokenReader.peekTokenKind())) {
551 var failure = this._createFailureForToken(tokenReader, TSDocMessageId.UnnecessaryBackslash, 'A backslash can only be used to escape a punctuation character');
552 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, 'Error reading inline TSDoc tag: ', failure);
553 }
554 tokenReader.readToken();
555 break;
556 case TokenKind.LeftCurlyBracket: {
557 var failure = this._createFailureForToken(tokenReader, TSDocMessageId.InlineTagUnescapedBrace, 'The "{" character must be escaped with a backslash when used inside a TSDoc inline tag');
558 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, marker, atSignMarker, '', failure);
559 }
560 case TokenKind.RightCurlyBracket:
561 done = true;
562 break;
563 default:
564 tokenReader.readToken();
565 break;
566 }
567 }
568 var tagContentExcerpt = tokenReader.tryExtractAccumulatedSequence();
569 // Read the right curly bracket
570 tokenReader.readToken();
571 var closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
572 var docInlineTagParsedParameters = {
573 parsed: true,
574 configuration: this._configuration,
575 openingDelimiterExcerpt: openingDelimiterExcerpt,
576 tagNameExcerpt: tagNameExcerpt,
577 tagName: tagName,
578 spacingAfterTagNameExcerpt: spacingAfterTagNameExcerpt,
579 tagContentExcerpt: tagContentExcerpt,
580 closingDelimiterExcerpt: closingDelimiterExcerpt
581 };
582 var tagNameWithUpperCase = tagName.toUpperCase();
583 // Create a new TokenReader that will reparse the tokens corresponding to the tagContent.
584 var embeddedTokenReader = new TokenReader(this._parserContext, tagContentExcerpt ? tagContentExcerpt : TokenSequence.createEmpty(this._parserContext));
585 var docNode;
586 switch (tagNameWithUpperCase) {
587 case StandardTags.inheritDoc.tagNameWithUpperCase:
588 docNode = this._parseInheritDocTag(docInlineTagParsedParameters, embeddedTokenReader);
589 break;
590 case StandardTags.link.tagNameWithUpperCase:
591 docNode = this._parseLinkTag(docInlineTagParsedParameters, embeddedTokenReader);
592 break;
593 default:
594 docNode = new DocInlineTag(docInlineTagParsedParameters);
595 }
596 // Validate the tag
597 var tagDefinition = this._parserContext.configuration.tryGetTagDefinitionWithUpperCase(tagNameWithUpperCase);
598 this._validateTagDefinition(tagDefinition, tagName,
599 /* expectingInlineTag */ true, tagNameExcerpt, docNode);
600 return docNode;
601 };
602 NodeParser.prototype._parseInheritDocTag = function (docInlineTagParsedParameters, embeddedTokenReader) {
603 // If an error occurs, then return a generic DocInlineTag instead of DocInheritDocTag
604 var errorTag = new DocInlineTag(docInlineTagParsedParameters);
605 var parameters = __assign({}, docInlineTagParsedParameters);
606 if (embeddedTokenReader.peekTokenKind() !== TokenKind.EndOfInput) {
607 parameters.declarationReference = this._parseDeclarationReference(embeddedTokenReader, docInlineTagParsedParameters.tagNameExcerpt, errorTag);
608 if (!parameters.declarationReference) {
609 return errorTag;
610 }
611 if (embeddedTokenReader.peekTokenKind() !== TokenKind.EndOfInput) {
612 embeddedTokenReader.readToken();
613 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.InheritDocTagSyntax, 'Unexpected character after declaration reference', embeddedTokenReader.extractAccumulatedSequence(), errorTag);
614 return errorTag;
615 }
616 }
617 return new DocInheritDocTag(parameters);
618 };
619 NodeParser.prototype._parseLinkTag = function (docInlineTagParsedParameters, embeddedTokenReader) {
620 // If an error occurs, then return a generic DocInlineTag instead of DocInheritDocTag
621 var errorTag = new DocInlineTag(docInlineTagParsedParameters);
622 var parameters = __assign({}, docInlineTagParsedParameters);
623 if (!docInlineTagParsedParameters.tagContentExcerpt) {
624 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.LinkTagEmpty, 'The @link tag content is missing', parameters.tagNameExcerpt, errorTag);
625 return errorTag;
626 }
627 // Is the link destination a URL or a declaration reference?
628 //
629 // The JSDoc "@link" tag allows URLs, however supporting full URLs would be highly
630 // ambiguous, for example "microsoft.windows.camera:" is an actual valid URI scheme,
631 // and even the common "mailto:example.com" looks suspiciously like a declaration reference.
632 // In practice JSDoc URLs are nearly always HTTP or HTTPS, so it seems fairly reasonable to
633 // require the URL to have "://" and a scheme without any punctuation in it. If a more exotic
634 // URL is needed, the HTML "<a>" tag can always be used.
635 // We start with a fairly broad classifier heuristic, and then the parsers will refine this:
636 // 1. Does it start with "//"?
637 // 2. Does it contain "://"?
638 var looksLikeUrl = embeddedTokenReader.peekTokenKind() === TokenKind.Slash &&
639 embeddedTokenReader.peekTokenAfterKind() === TokenKind.Slash;
640 var marker = embeddedTokenReader.createMarker();
641 var done = looksLikeUrl;
642 while (!done) {
643 switch (embeddedTokenReader.peekTokenKind()) {
644 // An URI scheme can contain letters, numbers, minus, plus, and periods
645 case TokenKind.AsciiWord:
646 case TokenKind.Period:
647 case TokenKind.Hyphen:
648 case TokenKind.Plus:
649 embeddedTokenReader.readToken();
650 break;
651 case TokenKind.Colon:
652 embeddedTokenReader.readToken();
653 // Once we a reach a colon, then it's a URL only if we see "://"
654 looksLikeUrl =
655 embeddedTokenReader.peekTokenKind() === TokenKind.Slash &&
656 embeddedTokenReader.peekTokenAfterKind() === TokenKind.Slash;
657 done = true;
658 break;
659 default:
660 done = true;
661 }
662 }
663 embeddedTokenReader.backtrackToMarker(marker);
664 // Is the hyperlink a URL or a declaration reference?
665 if (looksLikeUrl) {
666 // It starts with something like "http://", so parse it as a URL
667 if (!this._parseLinkTagUrlDestination(embeddedTokenReader, parameters, docInlineTagParsedParameters.tagNameExcerpt, errorTag)) {
668 return errorTag;
669 }
670 }
671 else {
672 // Otherwise, assume it's a declaration reference
673 if (!this._parseLinkTagCodeDestination(embeddedTokenReader, parameters, docInlineTagParsedParameters.tagNameExcerpt, errorTag)) {
674 return errorTag;
675 }
676 }
677 if (embeddedTokenReader.peekTokenKind() === TokenKind.Spacing) {
678 // The above parser rules should have consumed any spacing before the pipe
679 throw new Error('Unconsumed spacing encountered after construct');
680 }
681 if (embeddedTokenReader.peekTokenKind() === TokenKind.Pipe) {
682 // Read the link text
683 embeddedTokenReader.readToken();
684 parameters.pipeExcerpt = embeddedTokenReader.extractAccumulatedSequence();
685 parameters.spacingAfterPipeExcerpt = this._tryReadSpacingAndNewlines(embeddedTokenReader);
686 // Read everything until the end
687 // NOTE: Because we're using an embedded TokenReader, the TokenKind.EndOfInput occurs
688 // when we reach the "}", not the end of the original input
689 done = false;
690 var spacingAfterLinkTextMarker = undefined;
691 while (!done) {
692 switch (embeddedTokenReader.peekTokenKind()) {
693 case TokenKind.EndOfInput:
694 done = true;
695 break;
696 case TokenKind.Pipe:
697 case TokenKind.LeftCurlyBracket:
698 var badCharacter = embeddedTokenReader.readToken().toString();
699 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.LinkTagUnescapedText, "The \"" + badCharacter + "\" character may not be used in the link text without escaping it", embeddedTokenReader.extractAccumulatedSequence(), errorTag);
700 return errorTag;
701 case TokenKind.Spacing:
702 case TokenKind.Newline:
703 embeddedTokenReader.readToken();
704 break;
705 default:
706 // We found a non-spacing character, so move the spacingAfterLinkTextMarker
707 spacingAfterLinkTextMarker = embeddedTokenReader.createMarker() + 1;
708 embeddedTokenReader.readToken();
709 }
710 }
711 var linkTextAndSpacing = embeddedTokenReader.tryExtractAccumulatedSequence();
712 if (linkTextAndSpacing) {
713 if (spacingAfterLinkTextMarker === undefined) {
714 // We never found any non-spacing characters, so everything is trailing spacing
715 parameters.spacingAfterLinkTextExcerpt = linkTextAndSpacing;
716 }
717 else if (spacingAfterLinkTextMarker >= linkTextAndSpacing.endIndex) {
718 // We found no trailing spacing, so everything we found is the text
719 parameters.linkTextExcerpt = linkTextAndSpacing;
720 }
721 else {
722 // Split the trailing spacing from the link text
723 parameters.linkTextExcerpt = linkTextAndSpacing.getNewSequence(linkTextAndSpacing.startIndex, spacingAfterLinkTextMarker);
724 parameters.spacingAfterLinkTextExcerpt = linkTextAndSpacing.getNewSequence(spacingAfterLinkTextMarker, linkTextAndSpacing.endIndex);
725 }
726 }
727 }
728 else if (embeddedTokenReader.peekTokenKind() !== TokenKind.EndOfInput) {
729 embeddedTokenReader.readToken();
730 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.LinkTagDestinationSyntax, 'Unexpected character after link destination', embeddedTokenReader.extractAccumulatedSequence(), errorTag);
731 return errorTag;
732 }
733 return new DocLinkTag(parameters);
734 };
735 NodeParser.prototype._parseLinkTagUrlDestination = function (embeddedTokenReader, parameters, tokenSequenceForErrorContext, nodeForErrorContext) {
736 // Simply accumulate everything up to the next space. We won't try to implement a proper
737 // URI parser here.
738 var urlDestination = '';
739 var done = false;
740 while (!done) {
741 switch (embeddedTokenReader.peekTokenKind()) {
742 case TokenKind.Spacing:
743 case TokenKind.Newline:
744 case TokenKind.EndOfInput:
745 case TokenKind.Pipe:
746 case TokenKind.RightCurlyBracket:
747 done = true;
748 break;
749 default:
750 urlDestination += embeddedTokenReader.readToken();
751 break;
752 }
753 }
754 if (urlDestination.length === 0) {
755 // This should be impossible since the caller ensures that peekTokenKind() === TokenKind.AsciiWord
756 throw new Error('Missing URL in _parseLinkTagUrlDestination()');
757 }
758 var urlDestinationExcerpt = embeddedTokenReader.extractAccumulatedSequence();
759 var invalidUrlExplanation = StringChecks.explainIfInvalidLinkUrl(urlDestination);
760 if (invalidUrlExplanation) {
761 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.LinkTagInvalidUrl, invalidUrlExplanation, urlDestinationExcerpt, nodeForErrorContext);
762 return false;
763 }
764 parameters.urlDestinationExcerpt = urlDestinationExcerpt;
765 parameters.spacingAfterDestinationExcerpt = this._tryReadSpacingAndNewlines(embeddedTokenReader);
766 return true;
767 };
768 NodeParser.prototype._parseLinkTagCodeDestination = function (embeddedTokenReader, parameters, tokenSequenceForErrorContext, nodeForErrorContext) {
769 parameters.codeDestination = this._parseDeclarationReference(embeddedTokenReader, tokenSequenceForErrorContext, nodeForErrorContext);
770 return !!parameters.codeDestination;
771 };
772 NodeParser.prototype._parseDeclarationReference = function (tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) {
773 tokenReader.assertAccumulatedSequenceIsEmpty();
774 // The package name can contain characters that look like a member reference. This means we need to scan forwards
775 // to see if there is a "#". However, we need to be careful not to match a "#" that is part of a quoted expression.
776 var marker = tokenReader.createMarker();
777 var hasHash = false;
778 // A common mistake is to forget the "#" for package name or import path. The telltale sign
779 // of this is mistake is that we see path-only characters such as "@" or "/" in the beginning
780 // where this would be a syntax error for a member reference.
781 var lookingForImportCharacters = true;
782 var sawImportCharacters = false;
783 var done = false;
784 while (!done) {
785 switch (tokenReader.peekTokenKind()) {
786 case TokenKind.DoubleQuote:
787 case TokenKind.EndOfInput:
788 case TokenKind.LeftCurlyBracket:
789 case TokenKind.LeftParenthesis:
790 case TokenKind.LeftSquareBracket:
791 case TokenKind.Newline:
792 case TokenKind.Pipe:
793 case TokenKind.RightCurlyBracket:
794 case TokenKind.RightParenthesis:
795 case TokenKind.RightSquareBracket:
796 case TokenKind.SingleQuote:
797 case TokenKind.Spacing:
798 done = true;
799 break;
800 case TokenKind.PoundSymbol:
801 hasHash = true;
802 done = true;
803 break;
804 case TokenKind.Slash:
805 case TokenKind.AtSign:
806 if (lookingForImportCharacters) {
807 sawImportCharacters = true;
808 }
809 tokenReader.readToken();
810 break;
811 case TokenKind.AsciiWord:
812 case TokenKind.Period:
813 case TokenKind.Hyphen:
814 // It's a character that looks like part of a package name or import path,
815 // so don't set lookingForImportCharacters = false
816 tokenReader.readToken();
817 break;
818 default:
819 // Once we reach something other than AsciiWord and Period, then the meaning of
820 // slashes and at-signs is no longer obvious.
821 lookingForImportCharacters = false;
822 tokenReader.readToken();
823 }
824 }
825 if (!hasHash && sawImportCharacters) {
826 // We saw characters that will be a syntax error if interpreted as a member reference,
827 // but would make sense as a package name or import path, but we did not find a "#"
828 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingHash, 'The declaration reference appears to contain a package name or import path,' +
829 ' but it is missing the "#" delimiter', tokenReader.extractAccumulatedSequence(), nodeForErrorContext);
830 return undefined;
831 }
832 tokenReader.backtrackToMarker(marker);
833 var packageNameExcerpt;
834 var importPathExcerpt;
835 var importHashExcerpt;
836 var spacingAfterImportHashExcerpt;
837 if (hasHash) {
838 // If it starts with a "." then it's a relative path, not a package name
839 if (tokenReader.peekTokenKind() !== TokenKind.Period) {
840 // Read the package name:
841 var scopedPackageName = tokenReader.peekTokenKind() === TokenKind.AtSign;
842 var finishedScope = false;
843 done = false;
844 while (!done) {
845 switch (tokenReader.peekTokenKind()) {
846 case TokenKind.EndOfInput:
847 // If hasHash=true, then we are expecting to stop when we reach the hash
848 throw new Error('Expecting pound symbol');
849 case TokenKind.Slash:
850 // Stop at the first slash, unless this is a scoped package, in which case we stop at the second slash
851 if (scopedPackageName && !finishedScope) {
852 tokenReader.readToken();
853 finishedScope = true;
854 }
855 else {
856 done = true;
857 }
858 break;
859 case TokenKind.PoundSymbol:
860 done = true;
861 break;
862 default:
863 tokenReader.readToken();
864 }
865 }
866 if (!tokenReader.isAccumulatedSequenceEmpty()) {
867 packageNameExcerpt = tokenReader.extractAccumulatedSequence();
868 // Check that the packageName is syntactically valid
869 var explanation = StringChecks.explainIfInvalidPackageName(packageNameExcerpt.toString());
870 if (explanation) {
871 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMalformedPackageName, explanation, packageNameExcerpt, nodeForErrorContext);
872 return undefined;
873 }
874 }
875 }
876 // Read the import path:
877 done = false;
878 while (!done) {
879 switch (tokenReader.peekTokenKind()) {
880 case TokenKind.EndOfInput:
881 // If hasHash=true, then we are expecting to stop when we reach the hash
882 throw new Error('Expecting pound symbol');
883 case TokenKind.PoundSymbol:
884 done = true;
885 break;
886 default:
887 tokenReader.readToken();
888 }
889 }
890 if (!tokenReader.isAccumulatedSequenceEmpty()) {
891 importPathExcerpt = tokenReader.extractAccumulatedSequence();
892 // Check that the importPath is syntactically valid
893 var explanation = StringChecks.explainIfInvalidImportPath(importPathExcerpt.toString(), !!packageNameExcerpt);
894 if (explanation) {
895 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMalformedImportPath, explanation, importPathExcerpt, nodeForErrorContext);
896 return undefined;
897 }
898 }
899 // Read the import hash
900 if (tokenReader.peekTokenKind() !== TokenKind.PoundSymbol) {
901 // The above logic should have left us at the PoundSymbol
902 throw new Error('Expecting pound symbol');
903 }
904 tokenReader.readToken();
905 importHashExcerpt = tokenReader.extractAccumulatedSequence();
906 spacingAfterImportHashExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
907 if (packageNameExcerpt === undefined && importPathExcerpt === undefined) {
908 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceHashSyntax, 'The hash character must be preceded by a package name or import path', importHashExcerpt, nodeForErrorContext);
909 return undefined;
910 }
911 }
912 // Read the member references:
913 var memberReferences = [];
914 done = false;
915 while (!done) {
916 switch (tokenReader.peekTokenKind()) {
917 case TokenKind.Period:
918 case TokenKind.LeftParenthesis:
919 case TokenKind.AsciiWord:
920 case TokenKind.Colon:
921 case TokenKind.LeftSquareBracket:
922 case TokenKind.DoubleQuote:
923 var expectingDot = memberReferences.length > 0;
924 var memberReference = this._parseMemberReference(tokenReader, expectingDot, tokenSequenceForErrorContext, nodeForErrorContext);
925 if (!memberReference) {
926 return undefined;
927 }
928 memberReferences.push(memberReference);
929 break;
930 default:
931 done = true;
932 }
933 }
934 if (packageNameExcerpt === undefined &&
935 importPathExcerpt === undefined &&
936 memberReferences.length === 0) {
937 // We didn't find any parts of a declaration reference
938 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.MissingReference, 'Expecting a declaration reference', tokenSequenceForErrorContext, nodeForErrorContext);
939 return undefined;
940 }
941 return new DocDeclarationReference({
942 parsed: true,
943 configuration: this._configuration,
944 packageNameExcerpt: packageNameExcerpt,
945 importPathExcerpt: importPathExcerpt,
946 importHashExcerpt: importHashExcerpt,
947 spacingAfterImportHashExcerpt: spacingAfterImportHashExcerpt,
948 memberReferences: memberReferences
949 });
950 };
951 NodeParser.prototype._parseMemberReference = function (tokenReader, expectingDot, tokenSequenceForErrorContext, nodeForErrorContext) {
952 var parameters = {
953 parsed: true,
954 configuration: this._configuration
955 };
956 // Read the dot operator
957 if (expectingDot) {
958 if (tokenReader.peekTokenKind() !== TokenKind.Period) {
959 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingDot, 'Expecting a period before the next component of a declaration reference', tokenSequenceForErrorContext, nodeForErrorContext);
960 return undefined;
961 }
962 tokenReader.readToken();
963 parameters.dotExcerpt = tokenReader.extractAccumulatedSequence();
964 parameters.spacingAfterDotExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
965 }
966 // Read the left parenthesis if there is one
967 if (tokenReader.peekTokenKind() === TokenKind.LeftParenthesis) {
968 tokenReader.readToken();
969 parameters.leftParenthesisExcerpt = tokenReader.extractAccumulatedSequence();
970 parameters.spacingAfterLeftParenthesisExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
971 }
972 // Read the member identifier or symbol
973 if (tokenReader.peekTokenKind() === TokenKind.LeftSquareBracket) {
974 parameters.memberSymbol = this._parseMemberSymbol(tokenReader, nodeForErrorContext);
975 if (!parameters.memberSymbol) {
976 return undefined;
977 }
978 }
979 else {
980 parameters.memberIdentifier = this._parseMemberIdentifier(tokenReader, tokenSequenceForErrorContext, nodeForErrorContext);
981 if (!parameters.memberIdentifier) {
982 return undefined;
983 }
984 }
985 parameters.spacingAfterMemberExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
986 // Read the colon
987 if (tokenReader.peekTokenKind() === TokenKind.Colon) {
988 tokenReader.readToken();
989 parameters.colonExcerpt = tokenReader.extractAccumulatedSequence();
990 parameters.spacingAfterColonExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
991 if (!parameters.leftParenthesisExcerpt) {
992 // In the current TSDoc draft standard, a member reference with a selector requires the parentheses.
993 // It would be reasonable to make the parentheses optional, and we are contemplating simplifying the
994 // notation in the future. But for now the parentheses are required.
995 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceSelectorMissingParens, 'Syntax error in declaration reference: the member selector must be enclosed in parentheses', parameters.colonExcerpt, nodeForErrorContext);
996 return undefined;
997 }
998 // If there is a colon, then read the selector
999 parameters.selector = this._parseMemberSelector(tokenReader, parameters.colonExcerpt, nodeForErrorContext);
1000 if (!parameters.selector) {
1001 return undefined;
1002 }
1003 parameters.spacingAfterSelectorExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1004 }
1005 else {
1006 if (parameters.leftParenthesisExcerpt) {
1007 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingColon, 'Expecting a colon after the identifier because the expression is in parentheses', parameters.leftParenthesisExcerpt, nodeForErrorContext);
1008 return undefined;
1009 }
1010 }
1011 // Read the right parenthesis
1012 if (parameters.leftParenthesisExcerpt) {
1013 if (tokenReader.peekTokenKind() !== TokenKind.RightParenthesis) {
1014 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingRightParen, 'Expecting a matching right parenthesis', parameters.leftParenthesisExcerpt, nodeForErrorContext);
1015 return undefined;
1016 }
1017 tokenReader.readToken();
1018 parameters.rightParenthesisExcerpt = tokenReader.extractAccumulatedSequence();
1019 parameters.spacingAfterRightParenthesisExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1020 }
1021 return new DocMemberReference(parameters);
1022 };
1023 NodeParser.prototype._parseMemberSymbol = function (tokenReader, nodeForErrorContext) {
1024 // Read the "["
1025 if (tokenReader.peekTokenKind() !== TokenKind.LeftSquareBracket) {
1026 // This should be impossible since the caller ensures that peekTokenKind() === TokenKind.LeftSquareBracket
1027 throw new Error('Expecting "["');
1028 }
1029 tokenReader.readToken();
1030 var leftBracketExcerpt = tokenReader.extractAccumulatedSequence();
1031 var spacingAfterLeftBracketExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1032 // Read the declaration reference
1033 var declarationReference = this._parseDeclarationReference(tokenReader, leftBracketExcerpt, nodeForErrorContext);
1034 if (!declarationReference) {
1035 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceSymbolSyntax, 'Missing declaration reference in symbol reference', leftBracketExcerpt, nodeForErrorContext);
1036 return undefined;
1037 }
1038 // (We don't need to worry about spacing here since _parseDeclarationReference() absorbs trailing spaces)
1039 // Read the "]"
1040 if (tokenReader.peekTokenKind() !== TokenKind.RightSquareBracket) {
1041 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingRightBracket, 'Missing closing square bracket for symbol reference', leftBracketExcerpt, nodeForErrorContext);
1042 return undefined;
1043 }
1044 tokenReader.readToken();
1045 var rightBracketExcerpt = tokenReader.extractAccumulatedSequence();
1046 return new DocMemberSymbol({
1047 parsed: true,
1048 configuration: this._configuration,
1049 leftBracketExcerpt: leftBracketExcerpt,
1050 spacingAfterLeftBracketExcerpt: spacingAfterLeftBracketExcerpt,
1051 symbolReference: declarationReference,
1052 rightBracketExcerpt: rightBracketExcerpt
1053 });
1054 };
1055 NodeParser.prototype._parseMemberIdentifier = function (tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) {
1056 var leftQuoteExcerpt = undefined;
1057 var rightQuoteExcerpt = undefined;
1058 // Is this a quoted identifier?
1059 if (tokenReader.peekTokenKind() === TokenKind.DoubleQuote) {
1060 // Read the opening '"'
1061 tokenReader.readToken();
1062 leftQuoteExcerpt = tokenReader.extractAccumulatedSequence();
1063 // Read the text inside the quotes
1064 while (tokenReader.peekTokenKind() !== TokenKind.DoubleQuote) {
1065 if (tokenReader.peekTokenKind() === TokenKind.EndOfInput) {
1066 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingQuote, 'Unexpected end of input inside quoted member identifier', leftQuoteExcerpt, nodeForErrorContext);
1067 return undefined;
1068 }
1069 tokenReader.readToken();
1070 }
1071 if (tokenReader.isAccumulatedSequenceEmpty()) {
1072 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceEmptyIdentifier, 'The quoted identifier cannot be empty', leftQuoteExcerpt, nodeForErrorContext);
1073 return undefined;
1074 }
1075 var identifierExcerpt = tokenReader.extractAccumulatedSequence();
1076 // Read the closing '""
1077 tokenReader.readToken(); // read the quote
1078 rightQuoteExcerpt = tokenReader.extractAccumulatedSequence();
1079 return new DocMemberIdentifier({
1080 parsed: true,
1081 configuration: this._configuration,
1082 leftQuoteExcerpt: leftQuoteExcerpt,
1083 identifierExcerpt: identifierExcerpt,
1084 rightQuoteExcerpt: rightQuoteExcerpt
1085 });
1086 }
1087 else {
1088 // Otherwise assume it's a valid TypeScript identifier
1089 var done = false;
1090 while (!done) {
1091 switch (tokenReader.peekTokenKind()) {
1092 case TokenKind.AsciiWord:
1093 case TokenKind.DollarSign:
1094 tokenReader.readToken();
1095 break;
1096 default:
1097 done = true;
1098 break;
1099 }
1100 }
1101 if (tokenReader.isAccumulatedSequenceEmpty()) {
1102 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingIdentifier, 'Syntax error in declaration reference: expecting a member identifier', tokenSequenceForErrorContext, nodeForErrorContext);
1103 return undefined;
1104 }
1105 var identifierExcerpt = tokenReader.extractAccumulatedSequence();
1106 var identifier = identifierExcerpt.toString();
1107 var explanation = StringChecks.explainIfInvalidUnquotedMemberIdentifier(identifier);
1108 if (explanation) {
1109 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceUnquotedIdentifier, explanation, identifierExcerpt, nodeForErrorContext);
1110 return undefined;
1111 }
1112 return new DocMemberIdentifier({
1113 parsed: true,
1114 configuration: this._configuration,
1115 leftQuoteExcerpt: leftQuoteExcerpt,
1116 identifierExcerpt: identifierExcerpt,
1117 rightQuoteExcerpt: rightQuoteExcerpt
1118 });
1119 }
1120 };
1121 NodeParser.prototype._parseMemberSelector = function (tokenReader, tokenSequenceForErrorContext, nodeForErrorContext) {
1122 if (tokenReader.peekTokenKind() !== TokenKind.AsciiWord) {
1123 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceMissingLabel, 'Expecting a selector label after the colon', tokenSequenceForErrorContext, nodeForErrorContext);
1124 }
1125 var selector = tokenReader.readToken().toString();
1126 var selectorExcerpt = tokenReader.extractAccumulatedSequence();
1127 var docMemberSelector = new DocMemberSelector({
1128 parsed: true,
1129 configuration: this._configuration,
1130 selectorExcerpt: selectorExcerpt,
1131 selector: selector
1132 });
1133 if (docMemberSelector.errorMessage) {
1134 this._parserContext.log.addMessageForTokenSequence(TSDocMessageId.ReferenceSelectorSyntax, docMemberSelector.errorMessage, selectorExcerpt, nodeForErrorContext);
1135 return undefined;
1136 }
1137 return docMemberSelector;
1138 };
1139 NodeParser.prototype._parseHtmlStartTag = function (tokenReader) {
1140 tokenReader.assertAccumulatedSequenceIsEmpty();
1141 var marker = tokenReader.createMarker();
1142 // Read the "<" delimiter
1143 var lessThanToken = tokenReader.readToken();
1144 if (lessThanToken.kind !== TokenKind.LessThan) {
1145 // This would be a parser bug -- the caller of _parseHtmlStartTag() should have verified this while
1146 // looking ahead
1147 throw new Error('Expecting an HTML tag starting with "<"');
1148 }
1149 // NOTE: CommonMark does not permit whitespace after the "<"
1150 var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1151 // Read the element name
1152 var nameExcerpt = this._parseHtmlName(tokenReader);
1153 if (isFailure(nameExcerpt)) {
1154 return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'Invalid HTML element: ', nameExcerpt);
1155 }
1156 var spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1157 var htmlAttributes = [];
1158 // Read the attributes until we see a ">" or "/>"
1159 while (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
1160 // Read the attribute
1161 var attributeNode = this._parseHtmlAttribute(tokenReader);
1162 if (isFailure(attributeNode)) {
1163 return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'The HTML element has an invalid attribute: ', attributeNode);
1164 }
1165 htmlAttributes.push(attributeNode);
1166 }
1167 // Read the closing "/>" or ">" as the Excerpt.suffix
1168 tokenReader.assertAccumulatedSequenceIsEmpty();
1169 var endDelimiterMarker = tokenReader.createMarker();
1170 var selfClosingTag = false;
1171 if (tokenReader.peekTokenKind() === TokenKind.Slash) {
1172 tokenReader.readToken();
1173 selfClosingTag = true;
1174 }
1175 if (tokenReader.peekTokenKind() !== TokenKind.GreaterThan) {
1176 var failure = this._createFailureForTokensSince(tokenReader, TSDocMessageId.HtmlTagMissingGreaterThan, 'Expecting an attribute or ">" or "/>"', endDelimiterMarker);
1177 return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'The HTML tag has invalid syntax: ', failure);
1178 }
1179 tokenReader.readToken();
1180 var closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1181 // NOTE: We don't read excerptParameters.separator here, since if there is any it
1182 // will be represented as DocPlainText.
1183 return new DocHtmlStartTag({
1184 parsed: true,
1185 configuration: this._configuration,
1186 openingDelimiterExcerpt: openingDelimiterExcerpt,
1187 nameExcerpt: nameExcerpt,
1188 spacingAfterNameExcerpt: spacingAfterNameExcerpt,
1189 htmlAttributes: htmlAttributes,
1190 selfClosingTag: selfClosingTag,
1191 closingDelimiterExcerpt: closingDelimiterExcerpt
1192 });
1193 };
1194 NodeParser.prototype._parseHtmlAttribute = function (tokenReader) {
1195 tokenReader.assertAccumulatedSequenceIsEmpty();
1196 // Read the attribute name
1197 var nameExcerpt = this._parseHtmlName(tokenReader);
1198 if (isFailure(nameExcerpt)) {
1199 return nameExcerpt;
1200 }
1201 var spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1202 // Read the equals
1203 if (tokenReader.peekTokenKind() !== TokenKind.Equals) {
1204 return this._createFailureForToken(tokenReader, TSDocMessageId.HtmlTagMissingEquals, 'Expecting "=" after HTML attribute name');
1205 }
1206 tokenReader.readToken();
1207 var equalsExcerpt = tokenReader.extractAccumulatedSequence();
1208 var spacingAfterEqualsExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1209 // Read the attribute value
1210 var attributeValue = this._parseHtmlString(tokenReader);
1211 if (isFailure(attributeValue)) {
1212 return attributeValue;
1213 }
1214 var valueExcerpt = tokenReader.extractAccumulatedSequence();
1215 var spacingAfterValueExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1216 return new DocHtmlAttribute({
1217 parsed: true,
1218 configuration: this._configuration,
1219 nameExcerpt: nameExcerpt,
1220 spacingAfterNameExcerpt: spacingAfterNameExcerpt,
1221 equalsExcerpt: equalsExcerpt,
1222 spacingAfterEqualsExcerpt: spacingAfterEqualsExcerpt,
1223 valueExcerpt: valueExcerpt,
1224 spacingAfterValueExcerpt: spacingAfterValueExcerpt
1225 });
1226 };
1227 NodeParser.prototype._parseHtmlString = function (tokenReader) {
1228 var marker = tokenReader.createMarker();
1229 var quoteTokenKind = tokenReader.peekTokenKind();
1230 if (quoteTokenKind !== TokenKind.DoubleQuote && quoteTokenKind !== TokenKind.SingleQuote) {
1231 return this._createFailureForToken(tokenReader, TSDocMessageId.HtmlTagMissingString, 'Expecting an HTML string starting with a single-quote or double-quote character');
1232 }
1233 tokenReader.readToken();
1234 var textWithoutQuotes = '';
1235 for (;;) {
1236 var peekedTokenKind = tokenReader.peekTokenKind();
1237 // Did we find the matching token?
1238 if (peekedTokenKind === quoteTokenKind) {
1239 tokenReader.readToken(); // extract the quote
1240 break;
1241 }
1242 if (peekedTokenKind === TokenKind.EndOfInput || peekedTokenKind === TokenKind.Newline) {
1243 return this._createFailureForToken(tokenReader, TSDocMessageId.HtmlStringMissingQuote, 'The HTML string is missing its closing quote', marker);
1244 }
1245 textWithoutQuotes += tokenReader.readToken().toString();
1246 }
1247 // The next attribute cannot start immediately after this one
1248 if (tokenReader.peekTokenKind() === TokenKind.AsciiWord) {
1249 return this._createFailureForToken(tokenReader, TSDocMessageId.TextAfterHtmlString, 'The next character after a closing quote must be spacing or punctuation');
1250 }
1251 return textWithoutQuotes;
1252 };
1253 NodeParser.prototype._parseHtmlEndTag = function (tokenReader) {
1254 tokenReader.assertAccumulatedSequenceIsEmpty();
1255 var marker = tokenReader.createMarker();
1256 // Read the "</" delimiter
1257 var lessThanToken = tokenReader.peekToken();
1258 if (lessThanToken.kind !== TokenKind.LessThan) {
1259 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.MissingHtmlEndTag, 'Expecting an HTML tag starting with "</"');
1260 }
1261 tokenReader.readToken();
1262 var slashToken = tokenReader.peekToken();
1263 if (slashToken.kind !== TokenKind.Slash) {
1264 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.MissingHtmlEndTag, 'Expecting an HTML tag starting with "</"');
1265 }
1266 tokenReader.readToken();
1267 // NOTE: Spaces are not permitted here
1268 // https://www.w3.org/TR/html5/syntax.html#end-tags
1269 var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1270 // Read the tag name
1271 var nameExcerpt = this._parseHtmlName(tokenReader);
1272 if (isFailure(nameExcerpt)) {
1273 return this._backtrackAndCreateErrorForFailure(tokenReader, marker, 'Expecting an HTML element name: ', nameExcerpt);
1274 }
1275 var spacingAfterNameExcerpt = this._tryReadSpacingAndNewlines(tokenReader);
1276 // Read the closing ">"
1277 if (tokenReader.peekTokenKind() !== TokenKind.GreaterThan) {
1278 var failure = this._createFailureForToken(tokenReader, TSDocMessageId.HtmlTagMissingGreaterThan, 'Expecting a closing ">" for the HTML tag');
1279 return this._backtrackAndCreateErrorForFailure(tokenReader, marker, '', failure);
1280 }
1281 tokenReader.readToken();
1282 var closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1283 return new DocHtmlEndTag({
1284 parsed: true,
1285 configuration: this._configuration,
1286 openingDelimiterExcerpt: openingDelimiterExcerpt,
1287 nameExcerpt: nameExcerpt,
1288 spacingAfterNameExcerpt: spacingAfterNameExcerpt,
1289 closingDelimiterExcerpt: closingDelimiterExcerpt
1290 });
1291 };
1292 /**
1293 * Parses an HTML name such as an element name or attribute name.
1294 */
1295 NodeParser.prototype._parseHtmlName = function (tokenReader) {
1296 var marker = tokenReader.createMarker();
1297 if (tokenReader.peekTokenKind() === TokenKind.Spacing) {
1298 return this._createFailureForTokensSince(tokenReader, TSDocMessageId.MalformedHtmlName, 'A space is not allowed here', marker);
1299 }
1300 var done = false;
1301 while (!done) {
1302 switch (tokenReader.peekTokenKind()) {
1303 case TokenKind.Hyphen:
1304 case TokenKind.Period:
1305 case TokenKind.AsciiWord:
1306 tokenReader.readToken();
1307 break;
1308 default:
1309 done = true;
1310 break;
1311 }
1312 }
1313 var excerpt = tokenReader.tryExtractAccumulatedSequence();
1314 if (!excerpt) {
1315 return this._createFailureForToken(tokenReader, TSDocMessageId.MalformedHtmlName, 'Expecting an HTML name');
1316 }
1317 var htmlName = excerpt.toString();
1318 var explanation = StringChecks.explainIfInvalidHtmlName(htmlName);
1319 if (explanation) {
1320 return this._createFailureForTokensSince(tokenReader, TSDocMessageId.MalformedHtmlName, explanation, marker);
1321 }
1322 if (this._configuration.validation.reportUnsupportedHtmlElements &&
1323 !this._configuration.isHtmlElementSupported(htmlName)) {
1324 return this._createFailureForToken(tokenReader, TSDocMessageId.UnsupportedHtmlElementName, "The HTML element name " + JSON.stringify(htmlName) + " is not defined by your TSDoc configuration", marker);
1325 }
1326 return excerpt;
1327 };
1328 NodeParser.prototype._parseFencedCode = function (tokenReader) {
1329 tokenReader.assertAccumulatedSequenceIsEmpty();
1330 var startMarker = tokenReader.createMarker();
1331 var endOfOpeningDelimiterMarker = startMarker + 2;
1332 switch (tokenReader.peekPreviousTokenKind()) {
1333 case TokenKind.Newline:
1334 case TokenKind.EndOfInput:
1335 break;
1336 default:
1337 return this._backtrackAndCreateErrorRange(tokenReader, startMarker,
1338 // include the three backticks so they don't get reinterpreted as a code span
1339 endOfOpeningDelimiterMarker, TSDocMessageId.CodeFenceOpeningIndent, 'The opening backtick for a code fence must appear at the start of the line');
1340 }
1341 // Read the opening ``` delimiter
1342 var openingDelimiter = '';
1343 openingDelimiter += tokenReader.readToken();
1344 openingDelimiter += tokenReader.readToken();
1345 openingDelimiter += tokenReader.readToken();
1346 if (openingDelimiter !== '```') {
1347 // This would be a parser bug -- the caller of _parseFencedCode() should have verified this while
1348 // looking ahead to distinguish code spans/fences
1349 throw new Error('Expecting three backticks');
1350 }
1351 var openingFenceExcerpt = tokenReader.extractAccumulatedSequence();
1352 // Read any spaces after the delimiter,
1353 // but NOT the Newline since that goes with the spacingAfterLanguageExcerpt
1354 while (tokenReader.peekTokenKind() === TokenKind.Spacing) {
1355 tokenReader.readToken();
1356 }
1357 var spacingAfterOpeningFenceExcerpt = tokenReader.tryExtractAccumulatedSequence();
1358 // Read the language specifier (if present) and newline
1359 var done = false;
1360 var startOfPaddingMarker = undefined;
1361 while (!done) {
1362 switch (tokenReader.peekTokenKind()) {
1363 case TokenKind.Spacing:
1364 case TokenKind.Newline:
1365 if (startOfPaddingMarker === undefined) {
1366 // Starting a new run of spacing characters
1367 startOfPaddingMarker = tokenReader.createMarker();
1368 }
1369 if (tokenReader.peekTokenKind() === TokenKind.Newline) {
1370 done = true;
1371 }
1372 tokenReader.readToken();
1373 break;
1374 case TokenKind.Backtick:
1375 var failure = this._createFailureForToken(tokenReader, TSDocMessageId.CodeFenceSpecifierSyntax, 'The language specifier cannot contain backtick characters');
1376 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, startMarker, endOfOpeningDelimiterMarker, 'Error parsing code fence: ', failure);
1377 case TokenKind.EndOfInput:
1378 var failure2 = this._createFailureForToken(tokenReader, TSDocMessageId.CodeFenceMissingDelimiter, 'Missing closing delimiter');
1379 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, startMarker, endOfOpeningDelimiterMarker, 'Error parsing code fence: ', failure2);
1380 default:
1381 // more non-spacing content
1382 startOfPaddingMarker = undefined;
1383 tokenReader.readToken();
1384 break;
1385 }
1386 }
1387 // At this point, we must have accumulated at least a newline token.
1388 // Example: "pov-ray sdl \n"
1389 var restOfLineExcerpt = tokenReader.extractAccumulatedSequence();
1390 // Example: "pov-ray sdl"
1391 var languageExcerpt = restOfLineExcerpt.getNewSequence(restOfLineExcerpt.startIndex, startOfPaddingMarker);
1392 // Example: " \n"
1393 var spacingAfterLanguageExcerpt = restOfLineExcerpt.getNewSequence(startOfPaddingMarker, restOfLineExcerpt.endIndex);
1394 // Read the code content until we see the closing ``` delimiter
1395 var codeEndMarker = -1;
1396 var closingFenceStartMarker = -1;
1397 done = false;
1398 var tokenBeforeDelimiter;
1399 while (!done) {
1400 switch (tokenReader.peekTokenKind()) {
1401 case TokenKind.EndOfInput:
1402 var failure2 = this._createFailureForToken(tokenReader, TSDocMessageId.CodeFenceMissingDelimiter, 'Missing closing delimiter');
1403 return this._backtrackAndCreateErrorRangeForFailure(tokenReader, startMarker, endOfOpeningDelimiterMarker, 'Error parsing code fence: ', failure2);
1404 case TokenKind.Newline:
1405 tokenBeforeDelimiter = tokenReader.readToken();
1406 codeEndMarker = tokenReader.createMarker();
1407 while (tokenReader.peekTokenKind() === TokenKind.Spacing) {
1408 tokenBeforeDelimiter = tokenReader.readToken();
1409 }
1410 if (tokenReader.peekTokenKind() !== TokenKind.Backtick) {
1411 break;
1412 }
1413 closingFenceStartMarker = tokenReader.createMarker();
1414 tokenReader.readToken(); // first backtick
1415 if (tokenReader.peekTokenKind() !== TokenKind.Backtick) {
1416 break;
1417 }
1418 tokenReader.readToken(); // second backtick
1419 if (tokenReader.peekTokenKind() !== TokenKind.Backtick) {
1420 break;
1421 }
1422 tokenReader.readToken(); // third backtick
1423 done = true;
1424 break;
1425 default:
1426 tokenReader.readToken();
1427 break;
1428 }
1429 }
1430 if (tokenBeforeDelimiter.kind !== TokenKind.Newline) {
1431 this._parserContext.log.addMessageForTextRange(TSDocMessageId.CodeFenceClosingIndent, 'The closing delimiter for a code fence must not be indented', tokenBeforeDelimiter.range);
1432 }
1433 // Example: "code 1\ncode 2\n ```"
1434 var codeAndDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1435 // Example: "code 1\ncode 2\n"
1436 var codeExcerpt = codeAndDelimiterExcerpt.getNewSequence(codeAndDelimiterExcerpt.startIndex, codeEndMarker);
1437 // Example: " "
1438 var spacingBeforeClosingFenceExcerpt = codeAndDelimiterExcerpt.getNewSequence(codeEndMarker, closingFenceStartMarker);
1439 // Example: "```"
1440 var closingFenceExcerpt = codeAndDelimiterExcerpt.getNewSequence(closingFenceStartMarker, codeAndDelimiterExcerpt.endIndex);
1441 // Read the spacing and newline after the closing delimiter
1442 done = false;
1443 while (!done) {
1444 switch (tokenReader.peekTokenKind()) {
1445 case TokenKind.Spacing:
1446 tokenReader.readToken();
1447 break;
1448 case TokenKind.Newline:
1449 done = true;
1450 tokenReader.readToken();
1451 break;
1452 case TokenKind.EndOfInput:
1453 done = true;
1454 break;
1455 default:
1456 this._parserContext.log.addMessageForTextRange(TSDocMessageId.CodeFenceClosingSyntax, 'Unexpected characters after closing delimiter for code fence', tokenReader.peekToken().range);
1457 done = true;
1458 break;
1459 }
1460 }
1461 // Example: " \n"
1462 var spacingAfterClosingFenceExcerpt = tokenReader.tryExtractAccumulatedSequence();
1463 return new DocFencedCode({
1464 parsed: true,
1465 configuration: this._configuration,
1466 openingFenceExcerpt: openingFenceExcerpt,
1467 spacingAfterOpeningFenceExcerpt: spacingAfterOpeningFenceExcerpt,
1468 languageExcerpt: languageExcerpt,
1469 spacingAfterLanguageExcerpt: spacingAfterLanguageExcerpt,
1470 codeExcerpt: codeExcerpt,
1471 spacingBeforeClosingFenceExcerpt: spacingBeforeClosingFenceExcerpt,
1472 closingFenceExcerpt: closingFenceExcerpt,
1473 spacingAfterClosingFenceExcerpt: spacingAfterClosingFenceExcerpt
1474 });
1475 };
1476 NodeParser.prototype._parseCodeSpan = function (tokenReader) {
1477 tokenReader.assertAccumulatedSequenceIsEmpty();
1478 var marker = tokenReader.createMarker();
1479 // Parse the opening backtick
1480 if (tokenReader.peekTokenKind() !== TokenKind.Backtick) {
1481 // This would be a parser bug -- the caller of _parseCodeSpan() should have verified this while
1482 // looking ahead to distinguish code spans/fences
1483 throw new Error('Expecting a code span starting with a backtick character "`"');
1484 }
1485 tokenReader.readToken(); // read the backtick
1486 var openingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1487 var codeExcerpt = undefined;
1488 var closingDelimiterExcerpt = undefined;
1489 // Parse the content backtick
1490 for (;;) {
1491 var peekedTokenKind = tokenReader.peekTokenKind();
1492 // Did we find the matching token?
1493 if (peekedTokenKind === TokenKind.Backtick) {
1494 if (tokenReader.isAccumulatedSequenceEmpty()) {
1495 return this._backtrackAndCreateErrorRange(tokenReader, marker, marker + 1, TSDocMessageId.CodeSpanEmpty, 'A code span must contain at least one character between the backticks');
1496 }
1497 codeExcerpt = tokenReader.extractAccumulatedSequence();
1498 tokenReader.readToken();
1499 closingDelimiterExcerpt = tokenReader.extractAccumulatedSequence();
1500 break;
1501 }
1502 if (peekedTokenKind === TokenKind.EndOfInput || peekedTokenKind === TokenKind.Newline) {
1503 return this._backtrackAndCreateError(tokenReader, marker, TSDocMessageId.CodeSpanMissingDelimiter, 'The code span is missing its closing backtick');
1504 }
1505 tokenReader.readToken();
1506 }
1507 return new DocCodeSpan({
1508 parsed: true,
1509 configuration: this._configuration,
1510 openingDelimiterExcerpt: openingDelimiterExcerpt,
1511 codeExcerpt: codeExcerpt,
1512 closingDelimiterExcerpt: closingDelimiterExcerpt
1513 });
1514 };
1515 NodeParser.prototype._tryReadSpacingAndNewlines = function (tokenReader) {
1516 var done = false;
1517 do {
1518 switch (tokenReader.peekTokenKind()) {
1519 case TokenKind.Spacing:
1520 case TokenKind.Newline:
1521 tokenReader.readToken();
1522 break;
1523 default:
1524 done = true;
1525 break;
1526 }
1527 } while (!done);
1528 return tokenReader.tryExtractAccumulatedSequence();
1529 };
1530 /**
1531 * Read the next token, and report it as a DocErrorText node.
1532 */
1533 NodeParser.prototype._createError = function (tokenReader, messageId, errorMessage) {
1534 tokenReader.readToken();
1535 var textExcerpt = tokenReader.extractAccumulatedSequence();
1536 var docErrorText = new DocErrorText({
1537 parsed: true,
1538 configuration: this._configuration,
1539 textExcerpt: textExcerpt,
1540 messageId: messageId,
1541 errorMessage: errorMessage,
1542 errorLocation: textExcerpt
1543 });
1544 this._parserContext.log.addMessageForDocErrorText(docErrorText);
1545 return docErrorText;
1546 };
1547 /**
1548 * Rewind to the specified marker, read the next token, and report it as a DocErrorText node.
1549 */
1550 NodeParser.prototype._backtrackAndCreateError = function (tokenReader, marker, messageId, errorMessage) {
1551 tokenReader.backtrackToMarker(marker);
1552 return this._createError(tokenReader, messageId, errorMessage);
1553 };
1554 /**
1555 * Rewind to the errorStartMarker, read the tokens up to and including errorInclusiveEndMarker,
1556 * and report it as a DocErrorText node.
1557 */
1558 NodeParser.prototype._backtrackAndCreateErrorRange = function (tokenReader, errorStartMarker, errorInclusiveEndMarker, messageId, errorMessage) {
1559 tokenReader.backtrackToMarker(errorStartMarker);
1560 while (tokenReader.createMarker() !== errorInclusiveEndMarker) {
1561 tokenReader.readToken();
1562 }
1563 if (tokenReader.peekTokenKind() !== TokenKind.EndOfInput) {
1564 tokenReader.readToken();
1565 }
1566 var textExcerpt = tokenReader.extractAccumulatedSequence();
1567 var docErrorText = new DocErrorText({
1568 parsed: true,
1569 configuration: this._configuration,
1570 textExcerpt: textExcerpt,
1571 messageId: messageId,
1572 errorMessage: errorMessage,
1573 errorLocation: textExcerpt
1574 });
1575 this._parserContext.log.addMessageForDocErrorText(docErrorText);
1576 return docErrorText;
1577 };
1578 /**
1579 * Rewind to the specified marker, read the next token, and report it as a DocErrorText node
1580 * whose location is based on an IFailure.
1581 */
1582 NodeParser.prototype._backtrackAndCreateErrorForFailure = function (tokenReader, marker, errorMessagePrefix, failure) {
1583 tokenReader.backtrackToMarker(marker);
1584 tokenReader.readToken();
1585 var textExcerpt = tokenReader.extractAccumulatedSequence();
1586 var docErrorText = new DocErrorText({
1587 parsed: true,
1588 configuration: this._configuration,
1589 textExcerpt: textExcerpt,
1590 messageId: failure.failureMessageId,
1591 errorMessage: errorMessagePrefix + failure.failureMessage,
1592 errorLocation: failure.failureLocation
1593 });
1594 this._parserContext.log.addMessageForDocErrorText(docErrorText);
1595 return docErrorText;
1596 };
1597 /**
1598 * Rewind to the errorStartMarker, read the tokens up to and including errorInclusiveEndMarker,
1599 * and report it as a DocErrorText node whose location is based on an IFailure.
1600 */
1601 NodeParser.prototype._backtrackAndCreateErrorRangeForFailure = function (tokenReader, errorStartMarker, errorInclusiveEndMarker, errorMessagePrefix, failure) {
1602 tokenReader.backtrackToMarker(errorStartMarker);
1603 while (tokenReader.createMarker() !== errorInclusiveEndMarker) {
1604 tokenReader.readToken();
1605 }
1606 if (tokenReader.peekTokenKind() !== TokenKind.EndOfInput) {
1607 tokenReader.readToken();
1608 }
1609 var textExcerpt = tokenReader.extractAccumulatedSequence();
1610 var docErrorText = new DocErrorText({
1611 parsed: true,
1612 configuration: this._configuration,
1613 textExcerpt: textExcerpt,
1614 messageId: failure.failureMessageId,
1615 errorMessage: errorMessagePrefix + failure.failureMessage,
1616 errorLocation: failure.failureLocation
1617 });
1618 this._parserContext.log.addMessageForDocErrorText(docErrorText);
1619 return docErrorText;
1620 };
1621 /**
1622 * Creates an IFailure whose TokenSequence is a single token. If a marker is not specified,
1623 * then it is the current token.
1624 */
1625 NodeParser.prototype._createFailureForToken = function (tokenReader, failureMessageId, failureMessage, tokenMarker) {
1626 if (!tokenMarker) {
1627 tokenMarker = tokenReader.createMarker();
1628 }
1629 var tokenSequence = new TokenSequence({
1630 parserContext: this._parserContext,
1631 startIndex: tokenMarker,
1632 endIndex: tokenMarker + 1
1633 });
1634 return {
1635 failureMessageId: failureMessageId,
1636 failureMessage: failureMessage,
1637 failureLocation: tokenSequence
1638 };
1639 };
1640 /**
1641 * Creates an IFailure whose TokenSequence starts from the specified marker and
1642 * encompasses all tokens read since then. If none were read, then the next token used.
1643 */
1644 NodeParser.prototype._createFailureForTokensSince = function (tokenReader, failureMessageId, failureMessage, startMarker) {
1645 var endMarker = tokenReader.createMarker();
1646 if (endMarker < startMarker) {
1647 // This would be a parser bug
1648 throw new Error('Invalid startMarker');
1649 }
1650 if (endMarker === startMarker) {
1651 ++endMarker;
1652 }
1653 var tokenSequence = new TokenSequence({
1654 parserContext: this._parserContext,
1655 startIndex: startMarker,
1656 endIndex: endMarker
1657 });
1658 return {
1659 failureMessageId: failureMessageId,
1660 failureMessage: failureMessage,
1661 failureLocation: tokenSequence
1662 };
1663 };
1664 return NodeParser;
1665}());
1666export { NodeParser };
1667//# sourceMappingURL=NodeParser.js.map
\No newline at end of file