UNPKG

@microsoft/api-extractor

Version:

Validatation, documentation, and auditing for the exported API of a TypeScript package

314 lines (312 loc) 13.9 kB
/* tslint:disable:no-bitwise */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var DocElementParser_1 = require("../DocElementParser"); var ApiDefinitionReference_1 = require("../ApiDefinitionReference"); var Token_1 = require("../Token"); var Tokenizer_1 = require("../Tokenizer"); /** * An "API Tag" is a custom JSDoc tag which indicates whether an ApiItem definition * is considered Public API for third party developers, as well as its release * stage (alpha, beta, etc). * @see https://onedrive.visualstudio.com/DefaultCollection/SPPPlat/_git/sp-client * ?path=/common/docs/ApiPrinciplesAndProcess.md */ var ApiTag; (function (ApiTag) { /** * No API Tag was specified in the JSDoc summary. */ ApiTag[ApiTag["None"] = 0] = "None"; /** * The API was documented as internal, i.e. not callable by third party developers. */ ApiTag[ApiTag["Internal"] = 1] = "Internal"; /** * The API was documented as "alpha." This status is not generally used. See the * ApiPrinciplesAndProcess.md for details. */ ApiTag[ApiTag["Alpha"] = 2] = "Alpha"; /** * The API was documented as callable by third party developers, but at their own risk. * Web parts that call beta APIs should only be used for experimentation, because the Web Part * will break if Microsoft changes the API signature later. */ ApiTag[ApiTag["Beta"] = 3] = "Beta"; /** * The API was documented as callable by third party developers, with a guarantee that Microsoft will * never make any breaking changes once the API is published. */ ApiTag[ApiTag["Public"] = 4] = "Public"; })(ApiTag = exports.ApiTag || (exports.ApiTag = {})); var ApiDocumentation = (function () { function ApiDocumentation(docComment, referenceResolver, extractor, errorLogger, warnings) { this.originalJsDoc = docComment; this.referenceResolver = referenceResolver; this.extractor = extractor; 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. */ ApiDocumentation.prototype.completeInitialization = function (warnings) { // Ensure links are valid this._completeLinks(); // Ensure inheritdocs are valid this._completeInheritdocs(warnings); }; ApiDocumentation.prototype._parseDocs = function () { this.summary = []; this.returnsMessage = []; this.deprecatedMessage = []; this.remarks = []; this.incompleteLinks = []; this.incompleteInheritdocs = []; this.apiTag = ApiTag.None; var tokenizer = new Tokenizer_1.default(this.originalJsDoc, this.reportError); this.summary = DocElementParser_1.default.parse(this, tokenizer); var apiTagCount = 0; var parsing = true; while (parsing) { var 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.Tag) { switch (token.tag) { case '@remarks': tokenizer.getToken(); this._checkInheritDocStatus(); this.remarks = DocElementParser_1.default.parse(this, tokenizer); break; case '@returns': tokenizer.getToken(); this._checkInheritDocStatus(); this.returnsMessage = DocElementParser_1.default.parse(this, tokenizer); break; case '@param': tokenizer.getToken(); this._checkInheritDocStatus(); var param = this._parseParam(tokenizer); if (param) { this.parameters[param.name] = param; } break; case '@deprecated': tokenizer.getToken(); this.deprecatedMessage = DocElementParser_1.default.parse(this, tokenizer); if (!this.deprecatedMessage || this.deprecatedMessage.length === 0) { this.reportError("deprecated description required after @deprecated JSDoc tag."); } break; case '@internalremarks': // parse but discard tokenizer.getToken(); DocElementParser_1.default.parse(this, tokenizer); break; case '@public': tokenizer.getToken(); this.apiTag = ApiTag.Public; ++apiTagCount; break; case '@internal': tokenizer.getToken(); this.apiTag = ApiTag.Internal; ++apiTagCount; break; case '@alpha': tokenizer.getToken(); this.apiTag = ApiTag.Alpha; ++apiTagCount; break; case '@beta': tokenizer.getToken(); this.apiTag = ApiTag.Beta; ++apiTagCount; 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._reportBadJSDocTag(token); } } else if (token.type === Token_1.TokenType.Inline) { 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._reportBadJSDocTag(token); break; } } else if (token.type === Token_1.TokenType.Text) { tokenizer.getToken(); // Shorten "This is too long text" to "This is..." var MAX_LENGTH = 40; var problemText = token.text.trim(); if (problemText.length > MAX_LENGTH) { problemText = problemText.substr(0, MAX_LENGTH - 3).trim() + '...'; } this.reportError("Unexpected text in JSDoc comment: \"" + problemText + "\""); } else { tokenizer.getToken(); // This would be a program bug this.reportError("Unexpected token: " + token.type + " " + token.tag + " " + token.text); } } if (apiTagCount > 1) { this.reportError('More than one API Tag was specified'); } if (this.preapproved && this.apiTag !== ApiTag.Internal) { this.reportError('The @preapproved tag may only be applied to @internal defintions'); this.preapproved = false; } }; ApiDocumentation.prototype._parseParam = function (tokenizer) { var paramDescriptionToken = tokenizer.getToken(); if (!paramDescriptionToken) { this.reportError('@param tag missing required description'); return; } var hyphenIndex = paramDescriptionToken ? paramDescriptionToken.text.indexOf('-') : -1; if (hyphenIndex < 0) { this.reportError('No hyphens found in the @param line. ' + 'There should be a hyphen between the parameter name and its description.'); return; } else { var name_1 = paramDescriptionToken.text.slice(0, hyphenIndex).trim(); var comment = paramDescriptionToken.text.substr(hyphenIndex + 1).trim(); if (!comment) { this.reportError('@param tag requires a description following the hyphen'); return; } var commentTextElement = DocElementParser_1.default.makeTextElement(comment); // Full param description may contain additional Tokens (Ex: @link) var remainingElements = DocElementParser_1.default.parse(this, tokenizer); var descriptionElements = [commentTextElement].concat(remainingElements); var paramDocElement = { name: name_1, 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'. */ ApiDocumentation.prototype._completeLinks = function () { while (this.incompleteLinks.length) { var codeLink = this.incompleteLinks.pop(); var parts = { scopeName: codeLink.scopeName, packageName: codeLink.packageName, exportName: codeLink.exportName, memberName: codeLink.memberName }; var apiDefinitionRef = ApiDefinitionReference_1.default.createFromParts(parts); var resolvedApiItem = this.referenceResolver.resolve(apiDefinitionRef, this.extractor.package, this.warnings); // If the apiDefinitionRef can not be found the resolcedApiItem will be // undefined and an error will have been reported via this.reportError if (resolvedApiItem && resolvedApiItem.apiTag === ApiTag.Internal) { this.reportError('Unable to link to \"Internal\" API item'); } } }; /** * A processing of inheritdoc 'Tokens'. This processing occurs after we have created documentation * for all API items. */ ApiDocumentation.prototype._completeInheritdocs = function (warnings) { while (this.incompleteInheritdocs.length) { var token = this.incompleteInheritdocs.pop(); DocElementParser_1.default.parseInheritDoc(this, token, warnings); } }; ApiDocumentation.prototype._reportBadJSDocTag = function (token) { var supportsRegular = ApiDocumentation._allowedRegularJsdocTags.indexOf(token.tag) >= 0; var supportsInline = ApiDocumentation._allowedInlineJsdocTags.indexOf(token.tag) >= 0; if (!supportsRegular && !supportsInline) { this.reportError("Unknown JSDoc tag \"" + token.tag + "\""); return; } if (token.type === Token_1.TokenType.Inline && !supportsInline) { this.reportError("The JSDoc tag \"" + token.tag + "\" must not use the non-inline syntax (no curly braces)"); return; } if (token.type === Token_1.TokenType.Tag && !supportsRegular) { this.reportError("The JSDoc tag \"" + token.tag + "\" must use the inline syntax (with curly braces)"); return; } this.reportError("The JSDoc tag \"" + token.tag + "\" is not supported in this context"); return; }; ApiDocumentation.prototype._checkInheritDocStatus = function () { if (this.isDocInherited) { this.reportError('Cannot provide additional JSDoc tags if @inheritdoc tag is present'); } }; return ApiDocumentation; }()); /** * Match JsDoc block tags and inline tags * Example "@a @b@c d@e @f {whatever} {@link a} { @something } \@g" => ["@a", "@f", "{@link a}", "{ @something }"] */ ApiDocumentation._jsdocTagsRegex = /{\s*@(\\{|\\}|[^{}])*}|(?:^|\s)(\@[a-z_]+)(?=\s|$)/gi; // For guidance about using these tags, please see this document: // https://onedrive.visualstudio.com/DefaultCollection/SPPPlat/_git/sp-client // ?path=/common/docs/ApiPrinciplesAndProcess.md ApiDocumentation._allowedRegularJsdocTags = [ // (alphabetical order) '@alpha', '@beta', '@betadocumentation', '@internal', '@internalremarks', '@param', '@preapproved', '@public', '@returns', '@see', '@summary', '@deprecated', '@readonly', '@remarks' ]; ApiDocumentation._allowedInlineJsdocTags = [ // (alphabetical order) '@inheritdoc', '@link' ]; exports.default = ApiDocumentation; //# sourceMappingURL=ApiDocumentation.js.map