@microsoft/tsdoc
Version:
A parser for the TypeScript doc comment syntax
118 lines • 5.31 kB
JavaScript
import { DocExcerpt } from '../../nodes';
import { TokenSequence } from '../TokenSequence';
import { TokenKind } from '../Token';
/**
* The TokenCoverageChecker performs two diagnostics to detect parser bugs:
* 1. It checks for two DocNode objects whose excerpt contains overlapping tokens.
* By design, a single character from the input stream should be associated with
* at most one TokenSequence.
* 2. It checks for gaps, i.e. input tokens that were not associated with any DocNode
* (that is reachable from the final DocCommon node tree). In some cases this is
* okay. For example, if `@public` appears twice inside a comment, the second
* redundant instance is ignored. But in general we want to track the gaps in the
* unit test snapshots to ensure in general that every input character is associated
* with an excerpt for a DocNode.
*/
var TokenCoverageChecker = /** @class */ (function () {
function TokenCoverageChecker(parserContext) {
this._parserContext = parserContext;
this._tokenAssociations = [];
this._tokenAssociations.length = parserContext.tokens.length;
}
TokenCoverageChecker.prototype.getGaps = function (rootNode) {
this._addNodeTree(rootNode);
return this._checkForGaps(false);
};
TokenCoverageChecker.prototype.reportGaps = function (rootNode) {
this._addNodeTree(rootNode);
this._checkForGaps(true);
};
TokenCoverageChecker.prototype._addNodeTree = function (node) {
if (node instanceof DocExcerpt) {
this._addSequence(node.content, node);
}
for (var _i = 0, _a = node.getChildNodes(); _i < _a.length; _i++) {
var childNode = _a[_i];
this._addNodeTree(childNode);
}
};
TokenCoverageChecker.prototype._addSequence = function (tokenSequence, docNode) {
var newTokenAssociation = { docNode: docNode, tokenSequence: tokenSequence };
for (var i = tokenSequence.startIndex; i < tokenSequence.endIndex; ++i) {
var tokenAssociation = this._tokenAssociations[i];
if (tokenAssociation) {
throw new Error("Overlapping content encountered between" +
(" " + this._formatTokenAssociation(tokenAssociation) + " and") +
(" " + this._formatTokenAssociation(newTokenAssociation)));
}
this._tokenAssociations[i] = newTokenAssociation;
}
};
TokenCoverageChecker.prototype._checkForGaps = function (reportGaps) {
var gaps = [];
var gapStartIndex = undefined;
var tokenAssociationBeforeGap = undefined;
var tokens = this._parserContext.tokens;
if (tokens[tokens.length - 1].kind !== TokenKind.EndOfInput) {
throw new Error('Missing EndOfInput token');
}
for (var i = 0; i < this._parserContext.tokens.length - 1; ++i) {
var tokenAssociation = this._tokenAssociations[i];
if (gapStartIndex === undefined) {
// No gap found yet
if (tokenAssociation) {
tokenAssociationBeforeGap = tokenAssociation;
}
else {
// We found the start of a gap
gapStartIndex = i;
}
}
else {
// Is this the end of the gap?
if (tokenAssociation) {
var gap = new TokenSequence({
parserContext: this._parserContext,
startIndex: gapStartIndex,
endIndex: i
});
if (reportGaps) {
this._reportGap(gap, tokenAssociationBeforeGap, tokenAssociation);
}
gaps.push(gap);
gapStartIndex = undefined;
tokenAssociationBeforeGap = undefined;
}
}
}
if (gapStartIndex) {
var gap = new TokenSequence({
parserContext: this._parserContext,
startIndex: gapStartIndex,
endIndex: this._parserContext.tokens.length
});
if (reportGaps) {
this._reportGap(gap, tokenAssociationBeforeGap, undefined);
}
gaps.push(gap);
}
return gaps;
};
TokenCoverageChecker.prototype._reportGap = function (gap, tokenAssociationBeforeGap, tokenAssociationAfterGap) {
var message = 'Gap encountered';
if (tokenAssociationBeforeGap) {
message += ' before ' + this._formatTokenAssociation(tokenAssociationBeforeGap);
}
if (tokenAssociationAfterGap) {
message += ' after ' + this._formatTokenAssociation(tokenAssociationAfterGap);
}
message += ': ' + JSON.stringify(gap.toString());
throw new Error(message);
};
TokenCoverageChecker.prototype._formatTokenAssociation = function (tokenAssociation) {
return tokenAssociation.docNode.kind + " (" + JSON.stringify(tokenAssociation.tokenSequence.toString()) + ")";
};
return TokenCoverageChecker;
}());
export { TokenCoverageChecker };
//# sourceMappingURL=TokenCoverageChecker.js.map