diff --git a/src/configs/all.ts b/src/configs/all.ts index a4757694e8c..8d6387378f5 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -198,6 +198,7 @@ export const rules = { "no-boolean-literal-compare": true, "no-consecutive-blank-lines": true, "no-parameter-properties": true, + "no-redundant-jsdoc": true, "no-reference-import": true, "no-unnecessary-callback-wrapper": true, "no-unnecessary-initializer": true, diff --git a/src/language/formatter/formatter.ts b/src/language/formatter/formatter.ts index f4aaceb9ce8..f3c2b60e4bd 100644 --- a/src/language/formatter/formatter.ts +++ b/src/language/formatter/formatter.ts @@ -53,8 +53,8 @@ export interface FormatterConstructor { export interface IFormatter { /** * Formats linter results - * @param {RuleFailure[]} failures Linter failures that were not fixed - * @param {RuleFailure[]} fixes Fixed linter failures. Available when the `--fix` argument is used on the command line + * @param failures Linter failures that were not fixed + * @param fixes Fixed linter failures. Available when the `--fix` argument is used on the command line */ format(failures: RuleFailure[], fixes?: RuleFailure[]): string; } diff --git a/src/rules/noRedundantJsdocRule.ts b/src/rules/noRedundantJsdocRule.ts new file mode 100644 index 00000000000..4f9a604491c --- /dev/null +++ b/src/rules/noRedundantJsdocRule.ts @@ -0,0 +1,132 @@ +/** + * @license + * Copyright 2017 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getJsDoc } from "tsutils"; +import * as ts from "typescript"; + +import * as Lint from "../index"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + 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 */ + + public static FAILURE_STRING_REDUNDANT_TYPE = + "Type annotation in JSDoc is redundant in TypeScript code."; + public static FAILURE_STRING_REDUNDANT_TAG(tagName: string): string { + return `JSDoc tag '@${tagName}' is redundant in TypeScript code.`; + } + public static FAILURE_STRING_NO_COMMENT(tagName: string): string { + return `'@${tagName}' is redundant in TypeScript code if it has no description.`; + } + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk); + } +} + +function walk(ctx: Lint.WalkContext): void { + const { sourceFile } = ctx; + ts.forEachChild(sourceFile, function cb(node) { + for (const { tags } of getJsDoc(node, sourceFile)) { + if (tags !== undefined) { + for (const tag of tags) { + checkTag(tag); + } + } + } + ts.forEachChild(node, cb); + }); + + function checkTag(tag: ts.JSDocTag): void { + switch (tag.kind) { + case ts.SyntaxKind.JSDocTag: + if (redundantTags.has(tag.tagName.text)) { + ctx.addFailureAtNode(tag.tagName, Rule.FAILURE_STRING_REDUNDANT_TAG(tag.tagName.text)); + } + break; + + case ts.SyntaxKind.JSDocAugmentsTag: + // OK + break; + + case ts.SyntaxKind.JSDocTemplateTag: + case ts.SyntaxKind.JSDocTypeTag: + case ts.SyntaxKind.JSDocTypedefTag: + case ts.SyntaxKind.JSDocPropertyTag: + // Always redundant + ctx.addFailureAtNode(tag.tagName, Rule.FAILURE_STRING_REDUNDANT_TAG(tag.tagName.text)); + break; + + case ts.SyntaxKind.JSDocReturnTag: + case ts.SyntaxKind.JSDocParameterTag: { + const { typeExpression, comment } = tag as ts.JSDocReturnTag | ts.JSDocParameterTag; + if (typeExpression !== undefined) { + ctx.addFailureAtNode(typeExpression, Rule.FAILURE_STRING_REDUNDANT_TYPE); + } + if (comment === "") { + // Redundant if no documentation + ctx.addFailureAtNode(tag.tagName, Rule.FAILURE_STRING_NO_COMMENT(tag.tagName.text)); + } + break; + } + + default: + throw new Error(`Unexpected tag kind: ${ts.SyntaxKind[tag.kind]}`); + } + } +} + +const redundantTags = new Set([ + "abstract", + "access", + "class", + "constant", + "constructs", + "default", + "enum", + "exports", + "function", + "global", + "implements", + "interface", + "instance", + "member", + "memberof", + "mixes", + "mixin", + "module", + "name", + "namespace", + "override", + "private", + "property", + "protected", + "public", + "readonly", + "requires", + "static", + "this", +]); diff --git a/test/rules/no-redundant-jsdoc/test.ts.lint b/test/rules/no-redundant-jsdoc/test.ts.lint new file mode 100644 index 00000000000..c5669b39332 --- /dev/null +++ b/test/rules/no-redundant-jsdoc/test.ts.lint @@ -0,0 +1,35 @@ +/** @typedef {number} T */ + ~~~~~~~ [tag % ('typedef')] + +/** @function */ + ~~~~~~~~ [tag % ('function')] +function f() {} + +/** @type number */ + ~~~~ [tag % ('type')] +const x = 0; + +/** + * @param {number} x Is a number + ~~~~~~~~ [type] + * @param y + ~~~~~ [param] + * @param {number} z + ~~~~~ [param] + ~~~~~~~~ [type] + * @returns {number} + ~~~~~~~ [returns] + ~~~~~~~~ [type] + */ +declare function g(x: number, y: number, z: number): number; + +/** + * @param x Useful comment + * @returns Useful comment + */ +declare function h(x: number): number; + +[tag]: JSDoc tag '@%s' is redundant in TypeScript code. +[type]: Type annotation in JSDoc is redundant in TypeScript code. +[param]: '@param' is redundant in TypeScript code if it has no description. +[returns]: '@returns' is redundant in TypeScript code if it has no description. diff --git a/test/rules/no-redundant-jsdoc/tslint.json b/test/rules/no-redundant-jsdoc/tslint.json new file mode 100644 index 00000000000..b7cc878baac --- /dev/null +++ b/test/rules/no-redundant-jsdoc/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-redundant-jsdoc": true + } +} diff --git a/yarn.lock b/yarn.lock index 5b2e8d13ab2..6d419e2c347 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1591,8 +1591,8 @@ tsutils@^2.12.1: tslib "^1.7.1" tsutils@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.1.tgz#3771404e7ca9f0bedf5d919a47a4b1890a68efff" + version "2.10.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.10.0.tgz#ae94511df2656eb06e4424056fba5c388887040c" dependencies: tslib "^1.7.1"