UNPKG

@microsoft/api-extractor

Version:

Validate, document, and review the exported API for a TypeScript library

296 lines (294 loc) 13.2 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. Object.defineProperty(exports, "__esModule", { value: true }); const DocElementParser_1 = require("../DocElementParser"); const ApiDefinitionReference_1 = require("../ApiDefinitionReference"); const Token_1 = require("./Token"); const Tokenizer_1 = require("./Tokenizer"); const ReleaseTag_1 = require("./ReleaseTag"); const Markup_1 = require("../markup/Markup"); class ApiDocumentation { constructor(docComment, referenceResolver, context, errorLogger, warnings) { this.reportError = (message) => { errorLogger(message); this.failedToParse = true; }; this.originalAedoc = docComment; this.referenceResolver = referenceResolver; this.context = context; this.reportError = errorLogger; this.parameters = {}; this.warnings = warnings; this._parseDocs(); } /** * Executes the implementation details involved in completing the documentation initialization. * Currently completes link and inheritdocs. */ completeInitialization(warnings) { // Ensure links are valid this._completeLinks(); // Ensure inheritdocs are valid this._completeInheritdocs(warnings); } _parseDocs() { this.summary = []; this.returnsMessage = []; this.deprecatedMessage = []; this.remarks = []; this.incompleteLinks = []; this.incompleteInheritdocs = []; this.releaseTag = ReleaseTag_1.ReleaseTag.None; const tokenizer = new Tokenizer_1.default(this.originalAedoc, this.reportError); this.summary = DocElementParser_1.default.parseAndNormalize(this, tokenizer); let releaseTagCount = 0; let parsing = true; while (parsing) { const token = tokenizer.peekToken(); if (!token) { parsing = false; // end of stream // Report error if @inheritdoc is deprecated but no @deprecated tag present here if (this.isDocInheritedDeprecated && this.deprecatedMessage.length === 0) { // if this documentation inherits docs from a deprecated API item, then // this documentation must either have a deprecated message or it must // not use the @inheritdoc and copy+paste the documentation this.reportError(`A deprecation message must be included after the @deprecated tag.`); } break; } if (token.type === Token_1.TokenType.BlockTag) { switch (token.tag) { case '@remarks': tokenizer.getToken(); this._checkInheritDocStatus(token.tag); this.remarks = DocElementParser_1.default.parseAndNormalize(this, tokenizer); break; case '@returns': tokenizer.getToken(); this._checkInheritDocStatus(token.tag); this.returnsMessage = DocElementParser_1.default.parseAndNormalize(this, tokenizer); break; case '@param': tokenizer.getToken(); this._checkInheritDocStatus(token.tag); const param = this._parseParam(tokenizer); if (param) { this.parameters[param.name] = param; } break; case '@deprecated': tokenizer.getToken(); this.deprecatedMessage = DocElementParser_1.default.parseAndNormalize(this, tokenizer); if (!this.deprecatedMessage || this.deprecatedMessage.length === 0) { this.reportError(`deprecated description required after @deprecated AEDoc tag.`); } break; case '@internalremarks': // parse but discard tokenizer.getToken(); DocElementParser_1.default.parse(this, tokenizer); break; case '@public': tokenizer.getToken(); this.releaseTag = ReleaseTag_1.ReleaseTag.Public; ++releaseTagCount; break; case '@internal': tokenizer.getToken(); this.releaseTag = ReleaseTag_1.ReleaseTag.Internal; ++releaseTagCount; break; case '@alpha': tokenizer.getToken(); this.releaseTag = ReleaseTag_1.ReleaseTag.Alpha; ++releaseTagCount; break; case '@beta': tokenizer.getToken(); this.releaseTag = ReleaseTag_1.ReleaseTag.Beta; ++releaseTagCount; break; case '@preapproved': tokenizer.getToken(); this.preapproved = true; break; case '@readonly': tokenizer.getToken(); this.hasReadOnlyTag = true; break; case '@betadocumentation': tokenizer.getToken(); this.isDocBeta = true; break; default: tokenizer.getToken(); this._reportBadAedocTag(token); } } else if (token.type === Token_1.TokenType.InlineTag) { switch (token.tag) { case '@inheritdoc': DocElementParser_1.default.parse(this, tokenizer); break; case '@link': DocElementParser_1.default.parse(this, tokenizer); break; default: tokenizer.getToken(); this._reportBadAedocTag(token); break; } } else if (token.type === Token_1.TokenType.Text) { tokenizer.getToken(); if (token.text.trim().length !== 0) { // Shorten "This is too long text" to "This is..." const MAX_LENGTH = 40; let problemText = token.text.trim(); if (problemText.length > MAX_LENGTH) { problemText = problemText.substr(0, MAX_LENGTH - 3).trim() + '...'; } this.reportError(`Unexpected text in AEDoc comment: "${problemText}"`); } } else { tokenizer.getToken(); // This would be a program bug this.reportError(`Unexpected token: ${token.type} ${token.tag} "${token.text}"`); } } if (releaseTagCount > 1) { this.reportError('More than one release tag (@alpha, @beta, etc) was specified'); } if (this.preapproved && this.releaseTag !== ReleaseTag_1.ReleaseTag.Internal) { this.reportError('The @preapproved tag may only be applied to @internal definitions'); this.preapproved = false; } } _parseParam(tokenizer) { const paramDescriptionToken = tokenizer.getToken(); if (!paramDescriptionToken) { this.reportError('The @param tag is missing a parameter description'); return undefined; } const hyphenIndex = paramDescriptionToken ? paramDescriptionToken.text.indexOf('-') : -1; if (hyphenIndex < 0) { this.reportError('The @param tag is missing the hyphen that delimits the parameter name ' + ' and description'); return undefined; } else { const name = paramDescriptionToken.text.slice(0, hyphenIndex).trim(); const comment = paramDescriptionToken.text.substr(hyphenIndex + 1).trim(); if (!comment) { this.reportError('The @param tag is missing a parameter description'); return undefined; } const commentTextElements = Markup_1.Markup.createTextElements(comment); // Full param description may contain additional Tokens (Ex: @link) const remainingElements = DocElementParser_1.default.parse(this, tokenizer); const descriptionElements = commentTextElements.concat(remainingElements); Markup_1.Markup.normalize(descriptionElements); const paramDocElement = { name: name, description: descriptionElements }; return paramDocElement; } } /** * A processing of linkDocElements that refer to an ApiDefinitionReference. This method * ensures that the reference is to an API item that is not 'Internal'. */ _completeLinks() { for (;;) { const codeLink = this.incompleteLinks.pop(); if (!codeLink) { break; } const parts = { scopeName: codeLink.target.scopeName, packageName: codeLink.target.packageName, exportName: codeLink.target.exportName, memberName: codeLink.target.memberName }; const apiDefinitionRef = ApiDefinitionReference_1.default.createFromParts(parts); const resolvedAstItem = this.referenceResolver.resolve(apiDefinitionRef, this.context.package, this.warnings); // If the apiDefinitionRef can not be found the resolvedAstItem will be // undefined and an error will have been reported via this.reportError if (resolvedAstItem) { if (resolvedAstItem.releaseTag === ReleaseTag_1.ReleaseTag.Internal || resolvedAstItem.releaseTag === ReleaseTag_1.ReleaseTag.Alpha) { this.reportError('The {@link} tag references an @internal or @alpha API item, ' + 'which will not appear in the generated documentation'); } } } } /** * A processing of inheritdoc 'Tokens'. This processing occurs after we have created documentation * for all API items. */ _completeInheritdocs(warnings) { for (;;) { const token = this.incompleteInheritdocs.pop(); if (!token) { break; } DocElementParser_1.default.parseInheritDoc(this, token, warnings); } } _reportBadAedocTag(token) { const supportsRegular = ApiDocumentation._allowedRegularAedocTags.indexOf(token.tag) >= 0; const supportsInline = ApiDocumentation._allowedInlineAedocTags.indexOf(token.tag) >= 0; if (!supportsRegular && !supportsInline) { this.reportError(`The JSDoc tag \"${token.tag}\" is not supported by AEDoc`); return; } if (token.type === Token_1.TokenType.InlineTag && !supportsInline) { this.reportError(`The AEDoc tag \"${token.tag}\" must use the inline tag notation (i.e. with curly braces)`); return; } if (token.type === Token_1.TokenType.BlockTag && !supportsRegular) { this.reportError(`The AEDoc tag \"${token.tag}\" must use the block tag notation (i.e. no curly braces)`); return; } this.reportError(`The AEDoc tag \"${token.tag}\" is not supported in this context`); return; } _checkInheritDocStatus(aedocTag) { if (this.isDocInherited) { this.reportError(`The ${aedocTag} tag may not be used because this state is provided by the @inheritdoc target`); } } } /** * Match AEDoc block tags and inline tags * Example "@a @b@c d@e @f {whatever} {@link a} { @something } \@g" => ["@a", "@f", "{@link a}", "{ @something }"] */ ApiDocumentation._aedocTagsRegex = /{\s*@(\\{|\\}|[^{}])*}|(?:^|\s)(\@[a-z_]+)(?=\s|$)/gi; // For guidance about using these tags, please see this documentation: // https://github.com/Microsoft/web-build-tools/wiki/API-Extractor-~-AEDoc-tags ApiDocumentation._allowedRegularAedocTags = [ // (alphabetical order) '@alpha', '@beta', '@betadocumentation', '@internal', '@internalremarks', '@param', '@preapproved', '@public', '@returns', '@deprecated', '@readonly', '@remarks' ]; ApiDocumentation._allowedInlineAedocTags = [ // (alphabetical order) '@inheritdoc', '@link' ]; exports.default = ApiDocumentation; //# sourceMappingURL=ApiDocumentation.js.map