1 | {"version":3,"file":"TokenCoverageChecker.js","sourceRoot":"","sources":["../../../src/parser/__tests__/TokenCoverageChecker.ts"],"names":[],"mappings":"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","sourcesContent":["import { DocNode, DocExcerpt } from '../../nodes';\r\nimport { TokenSequence } from '../TokenSequence';\r\nimport { ParserContext } from '../ParserContext';\r\nimport { TokenKind, Token } from '../Token';\r\n\r\ninterface ITokenAssociation {\r\n docNode: DocNode;\r\n tokenSequence: TokenSequence;\r\n}\r\n\r\n/**\r\n * The TokenCoverageChecker performs two diagnostics to detect parser bugs:\r\n * 1. It checks for two DocNode objects whose excerpt contains overlapping tokens.\r\n * By design, a single character from the input stream should be associated with\r\n * at most one TokenSequence.\r\n * 2. It checks for gaps, i.e. input tokens that were not associated with any DocNode\r\n * (that is reachable from the final DocCommon node tree). In some cases this is\r\n * okay. For example, if `@public` appears twice inside a comment, the second\r\n * redundant instance is ignored. But in general we want to track the gaps in the\r\n * unit test snapshots to ensure in general that every input character is associated\r\n * with an excerpt for a DocNode.\r\n */\r\nexport class TokenCoverageChecker {\r\n private readonly _parserContext: ParserContext;\r\n private readonly _tokenAssociations: (ITokenAssociation | undefined)[];\r\n\r\n public constructor(parserContext: ParserContext) {\r\n this._parserContext = parserContext;\r\n this._tokenAssociations = [];\r\n this._tokenAssociations.length = parserContext.tokens.length;\r\n }\r\n\r\n public getGaps(rootNode: DocNode): TokenSequence[] {\r\n this._addNodeTree(rootNode);\r\n return this._checkForGaps(false);\r\n }\r\n\r\n public reportGaps(rootNode: DocNode): void {\r\n this._addNodeTree(rootNode);\r\n this._checkForGaps(true);\r\n }\r\n\r\n private _addNodeTree(node: DocNode): void {\r\n if (node instanceof DocExcerpt) {\r\n this._addSequence(node.content, node);\r\n }\r\n\r\n for (const childNode of node.getChildNodes()) {\r\n this._addNodeTree(childNode);\r\n }\r\n }\r\n\r\n private _addSequence(tokenSequence: TokenSequence, docNode: DocNode): void {\r\n const newTokenAssociation: ITokenAssociation = { docNode, tokenSequence };\r\n\r\n for (let i: number = tokenSequence.startIndex; i < tokenSequence.endIndex; ++i) {\r\n const tokenAssociation: ITokenAssociation | undefined = this._tokenAssociations[i];\r\n if (tokenAssociation) {\r\n throw new Error(\r\n `Overlapping content encountered between` +\r\n ` ${this._formatTokenAssociation(tokenAssociation)} and` +\r\n ` ${this._formatTokenAssociation(newTokenAssociation)}`\r\n );\r\n }\r\n\r\n this._tokenAssociations[i] = newTokenAssociation;\r\n }\r\n }\r\n\r\n private _checkForGaps(reportGaps: boolean): TokenSequence[] {\r\n const gaps: TokenSequence[] = [];\r\n\r\n let gapStartIndex: number | undefined = undefined;\r\n let tokenAssociationBeforeGap: ITokenAssociation | undefined = undefined;\r\n\r\n const tokens: Token[] = this._parserContext.tokens;\r\n if (tokens[tokens.length - 1].kind !== TokenKind.EndOfInput) {\r\n throw new Error('Missing EndOfInput token');\r\n }\r\n\r\n for (let i: number = 0; i < this._parserContext.tokens.length - 1; ++i) {\r\n const tokenAssociation: ITokenAssociation | undefined = this._tokenAssociations[i];\r\n\r\n if (gapStartIndex === undefined) {\r\n // No gap found yet\r\n\r\n if (tokenAssociation) {\r\n tokenAssociationBeforeGap = tokenAssociation;\r\n } else {\r\n // We found the start of a gap\r\n gapStartIndex = i;\r\n }\r\n } else {\r\n // Is this the end of the gap?\r\n if (tokenAssociation) {\r\n const gap: TokenSequence = new TokenSequence({\r\n parserContext: this._parserContext,\r\n startIndex: gapStartIndex,\r\n endIndex: i\r\n });\r\n if (reportGaps) {\r\n this._reportGap(gap, tokenAssociationBeforeGap, tokenAssociation);\r\n }\r\n gaps.push(gap);\r\n\r\n gapStartIndex = undefined;\r\n tokenAssociationBeforeGap = undefined;\r\n }\r\n }\r\n }\r\n\r\n if (gapStartIndex) {\r\n const gap: TokenSequence = new TokenSequence({\r\n parserContext: this._parserContext,\r\n startIndex: gapStartIndex,\r\n endIndex: this._parserContext.tokens.length\r\n });\r\n if (reportGaps) {\r\n this._reportGap(gap, tokenAssociationBeforeGap, undefined);\r\n }\r\n gaps.push(gap);\r\n }\r\n\r\n return gaps;\r\n }\r\n\r\n private _reportGap(\r\n gap: TokenSequence,\r\n tokenAssociationBeforeGap: ITokenAssociation | undefined,\r\n tokenAssociationAfterGap: ITokenAssociation | undefined\r\n ): never {\r\n let message: string = 'Gap encountered';\r\n\r\n if (tokenAssociationBeforeGap) {\r\n message += ' before ' + this._formatTokenAssociation(tokenAssociationBeforeGap);\r\n }\r\n\r\n if (tokenAssociationAfterGap) {\r\n message += ' after ' + this._formatTokenAssociation(tokenAssociationAfterGap);\r\n }\r\n\r\n message += ': ' + JSON.stringify(gap.toString());\r\n throw new Error(message);\r\n }\r\n\r\n private _formatTokenAssociation(tokenAssociation: ITokenAssociation): string {\r\n return `${tokenAssociation.docNode.kind} (${JSON.stringify(tokenAssociation.tokenSequence.toString())})`;\r\n }\r\n}\r\n"]} |