UNPKG

dtslint

Version:

Runs tests on TypeScript definition files

246 lines 9.51 kB
"use strict"; // Fixes temporarily moved here until they are published by tslint. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Rule = void 0; const assert = require("assert"); const Lint = __importStar(require("tslint")); const tsutils_1 = require("tsutils"); const ts = __importStar(require("typescript")); class Rule extends Lint.Rules.AbstractRule { static FAILURE_STRING_REDUNDANT_TAG(tagName) { return `JSDoc tag '@${tagName}' is redundant in TypeScript code.`; } static FAILURE_STRING_NO_COMMENT(tagName) { return `'@${tagName}' is redundant in TypeScript code if it has no description.`; } apply(sourceFile) { return this.applyWithFunction(sourceFile, walk); } } exports.Rule = Rule; /* tslint:disable:object-literal-sort-keys */ Rule.metadata = { ruleName: "no-redundant-jsdoc", description: "Forbids JSDoc which duplicates TypeScript functionality.", optionsDescription: "Not configurable.", options: null, optionExamples: [true], type: "style", typescriptOnly: true, }; /* tslint:enable:object-literal-sort-keys */ Rule.FAILURE_STRING_REDUNDANT_TYPE = "Type annotation in JSDoc is redundant in TypeScript code."; Rule.FAILURE_STRING_EMPTY = "JSDoc comment is empty."; function walk(ctx) { const { sourceFile } = ctx; // Intentionally exclude EndOfFileToken: it can have JSDoc, but it is only relevant in JavaScript files return sourceFile.statements.forEach(function cb(node) { if ((0, tsutils_1.canHaveJsDoc)(node)) { for (const jd of (0, tsutils_1.getJsDoc)(node, sourceFile)) { const { tags } = jd; if (tags === undefined || tags.length === 0) { if (jd.comment === undefined) { ctx.addFailureAtNode(jd, Rule.FAILURE_STRING_EMPTY, Lint.Replacement.deleteFromTo(jd.getStart(sourceFile), jd.getEnd())); } } else { for (const tag of tags) { checkTag(tag); } } } } return ts.forEachChild(node, cb); }); function checkTag(tag) { const jsdocSeeTag = ts.SyntaxKind.JSDocSeeTag || 0; const jsdocDeprecatedTag = ts.SyntaxKind.JSDocDeprecatedTag || 0; switch (tag.kind) { case jsdocSeeTag: case jsdocDeprecatedTag: case ts.SyntaxKind.JSDocAuthorTag: // @deprecated and @see always have meaning break; case ts.SyntaxKind.JSDocTag: { const { tagName } = tag; const { text } = tagName; // Allow "default" in an ambient context (since you can't write an initializer in an ambient context) if (redundantTags.has(text) && !(text === "default" && isInAmbientContext(tag))) { ctx.addFailureAtNode(tagName, Rule.FAILURE_STRING_REDUNDANT_TAG(text), removeTag(tag, sourceFile)); } break; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore (fallthrough) case ts.SyntaxKind.JSDocTemplateTag: if (tag.comment !== "") { break; } // falls through case ts.SyntaxKind.JSDocPublicTag: case ts.SyntaxKind.JSDocPrivateTag: case ts.SyntaxKind.JSDocProtectedTag: case ts.SyntaxKind.JSDocClassTag: case ts.SyntaxKind.JSDocTypeTag: case ts.SyntaxKind.JSDocTypedefTag: case ts.SyntaxKind.JSDocReadonlyTag: case ts.SyntaxKind.JSDocPropertyTag: case ts.SyntaxKind.JSDocAugmentsTag: case ts.SyntaxKind.JSDocImplementsTag: case ts.SyntaxKind.JSDocCallbackTag: case ts.SyntaxKind.JSDocThisTag: case ts.SyntaxKind.JSDocEnumTag: // Always redundant ctx.addFailureAtNode(tag.tagName, Rule.FAILURE_STRING_REDUNDANT_TAG(tag.tagName.text), removeTag(tag, sourceFile)); break; case ts.SyntaxKind.JSDocReturnTag: case ts.SyntaxKind.JSDocParameterTag: { const { typeExpression, comment } = tag; const noComment = comment === ""; if (typeExpression !== undefined) { // If noComment, we will just completely remove it in the other fix const fix = noComment ? undefined : removeTypeExpression(typeExpression, sourceFile); ctx.addFailureAtNode(typeExpression, Rule.FAILURE_STRING_REDUNDANT_TYPE, fix); } if (noComment) { // Redundant if no documentation ctx.addFailureAtNode(tag.tagName, Rule.FAILURE_STRING_NO_COMMENT(tag.tagName.text), removeTag(tag, sourceFile)); } break; } default: throw new Error(`Unexpected tag kind: ${ts.SyntaxKind[tag.kind]}`); } } } function removeTag(tag, sourceFile) { const { text } = sourceFile; const jsdoc = tag.parent; if (jsdoc.kind === ts.SyntaxKind.JSDocTypeLiteral) { return undefined; } if (jsdoc.comment === undefined && jsdoc.tags.length === 1) { // This is the only tag -- remove the whole comment return Lint.Replacement.deleteFromTo(jsdoc.getStart(sourceFile), jsdoc.getEnd()); } let start = tag.getStart(sourceFile); assert(text[start] === "@"); start--; while (ts.isWhiteSpaceSingleLine(text.charCodeAt(start))) { start--; } if (text[start] !== "*") { return undefined; } let end = tag.getEnd(); // For some tags, like `@param`, `end` will be the start of the next tag. // For some tags, like `@name`, `end` will be before the start of the comment. // And `@typedef` doesn't end until the last `@property` tag attached to it ends. switch (tag.tagName.text) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore (fallthrough) case "param": { const { isBracketed, isNameFirst, typeExpression } = tag; if (!isBracketed && !(isNameFirst && typeExpression !== undefined)) { break; } // falls through } // eslint-disable-next-line no-fallthrough case "name": case "return": case "returns": case "interface": case "default": case "memberof": case "memberOf": case "method": case "type": case "class": case "property": case "function": end--; // Might end with "\n" (test with just `@return` with no comment or type) // For some reason, for "@name", "end" is before the start of the comment part of the tag. // Also for "param" if the name is optional as in `@param {number} [x]` while (!ts.isLineBreak(text.charCodeAt(end))) { end++; } end++; } while (ts.isWhiteSpaceSingleLine(text.charCodeAt(end))) { end++; } if (text[end] !== "*") { return undefined; } return Lint.Replacement.deleteFromTo(start, end); } function removeTypeExpression(typeExpression, sourceFile) { const start = typeExpression.getStart(sourceFile); let end = typeExpression.getEnd(); const { text } = sourceFile; if (text[start] !== "{" || text[end - 1] !== "}") { // TypeScript parser messed up -- give up return undefined; } if (ts.isWhiteSpaceSingleLine(text.charCodeAt(end))) { end++; } return Lint.Replacement.deleteFromTo(start, end); } // TODO: improve once https://github.com/Microsoft/TypeScript/pull/17831 is in function isInAmbientContext(node) { return ts.isSourceFile(node) ? node.isDeclarationFile : Lint.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword) || isInAmbientContext(node.parent); } const redundantTags = new Set([ "abstract", "access", "class", "constant", "constructs", "default", "enum", "export", "exports", "function", "global", "inherits", "interface", "instance", "member", "method", "memberof", "memberOf", "mixes", "mixin", "module", "name", "namespace", "override", "property", "requires", "static", "this", ]); //# sourceMappingURL=noRedundantJsdoc2Rule.js.map