From 7099954d1213d081a0c92a09e4ed8c588c745b69 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 2 Mar 2021 19:27:57 +0900 Subject: [PATCH] Add `ignorePublicMembers` option to `vue/no-unused-properties` rule (#1444) --- docs/rules/no-unused-properties.md | 36 ++++- lib/rules/no-unused-properties.js | 95 ++++++++++- tests/lib/rules/no-unused-properties.js | 206 ++++++++++++++++++++++++ typings/eslint-utils/index.d.ts | 3 + 4 files changed, 336 insertions(+), 4 deletions(-) diff --git a/docs/rules/no-unused-properties.md b/docs/rules/no-unused-properties.md index 932fb9563..da8fa7d87 100644 --- a/docs/rules/no-unused-properties.md +++ b/docs/rules/no-unused-properties.md @@ -55,18 +55,20 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property { "vue/no-unused-properties": ["error", { "groups": ["props"], - "deepData": false + "deepData": false, + "ignorePublicMembers": false }] } ``` -- `"groups"` (`string[]`) Array of groups to search for properties. Default is `["props"]`. The value of the array is some of the following strings: +- `groups` (`string[]`) Array of groups to search for properties. Default is `["props"]`. The value of the array is some of the following strings: - `"props"` - `"data"` - `"computed"` - `"methods"` - `"setup"` -- `"deepData"` (`boolean`) If `true`, the object of the property defined in `data` will be searched deeply. Default is `false`. Include `"data"` in `groups` to use this option. +- `deepData` (`boolean`) If `true`, the object of the property defined in `data` will be searched deeply. Default is `false`. Include `"data"` in `groups` to use this option. +- `ignorePublicMembers` (`boolean`) If `true`, members marked with a [JSDoc `/** @public */` tag](https://jsdoc.app/tags-public.html) will be ignored. Default is `false`. ### `"groups": ["props", "data"]` @@ -188,6 +190,34 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property +### `{ "groups": ["props", "methods"], "ignorePublicMembers": true }` + + + +```vue + + + +``` + + + ## :rocket: Version This rule was introduced in eslint-plugin-vue v7.0.0 diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js index f89c51878..be18224ed 100644 --- a/lib/rules/no-unused-properties.js +++ b/lib/rules/no-unused-properties.js @@ -459,6 +459,91 @@ function getObjectPatternPropertyPatternTracker(pattern) { return () => new UsedProperties({ unknown: true }) } +/** + * Check if the given component property is marked as `@public` in JSDoc comments. + * @param {ComponentPropertyData} property + * @param {SourceCode} sourceCode + */ +function isPublicMember(property, sourceCode) { + if ( + property.type === 'object' && + // Props do not support @public. + property.groupName !== 'props' + ) { + return isPublicProperty(property.property, sourceCode) + } + return false +} + +/** + * Check if the given property node is marked as `@public` in JSDoc comments. + * @param {Property} node + * @param {SourceCode} sourceCode + */ +function isPublicProperty(node, sourceCode) { + const jsdoc = getJSDocFromProperty(node, sourceCode) + if (jsdoc) { + return /(?:^|\s|\*)@public\b/u.test(jsdoc.value) + } + return false +} + +/** + * Get the JSDoc comment for a given property node. + * @param {Property} node + * @param {SourceCode} sourceCode + */ +function getJSDocFromProperty(node, sourceCode) { + const jsdoc = findJSDocComment(node, sourceCode) + if (jsdoc) { + return jsdoc + } + if ( + node.value.type === 'FunctionExpression' || + node.value.type === 'ArrowFunctionExpression' + ) { + return findJSDocComment(node.value, sourceCode) + } + + return null +} + +/** + * Finds a JSDoc comment for the given node. + * @param {ASTNode} node + * @param {SourceCode} sourceCode + * @returns {Comment | null} + */ +function findJSDocComment(node, sourceCode) { + /** @type {ASTNode | Token} */ + let currentNode = node + let tokenBefore = null + + while (currentNode) { + tokenBefore = sourceCode.getTokenBefore(currentNode, { + includeComments: true + }) + if (!tokenBefore || !eslintUtils.isCommentToken(tokenBefore)) { + return null + } + if (tokenBefore.type === 'Line') { + currentNode = tokenBefore + continue + } + break + } + + if ( + tokenBefore && + tokenBefore.type === 'Block' && + tokenBefore.value.charAt(0) === '*' + ) { + return tokenBefore + } + + return null +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -490,7 +575,8 @@ module.exports = { additionalItems: false, uniqueItems: true }, - deepData: { type: 'boolean' } + deepData: { type: 'boolean' }, + ignorePublicMembers: { type: 'boolean' } }, additionalProperties: false } @@ -504,6 +590,7 @@ module.exports = { const options = context.options[0] || {} const groups = new Set(options.groups || [GROUP_PROPERTY]) const deepData = Boolean(options.deepData) + const ignorePublicMembers = Boolean(options.ignorePublicMembers) /** @type {Map} */ const paramsUsedPropertiesMap = new Map() @@ -626,6 +713,12 @@ module.exports = { // used template refs continue } + if ( + ignorePublicMembers && + isPublicMember(property, context.getSourceCode()) + ) { + continue + } if (usedProperties.isUsed(property.name)) { // used if ( diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js index eae813bb2..e52a4bbf4 100644 --- a/tests/lib/rules/no-unused-properties.js +++ b/tests/lib/rules/no-unused-properties.js @@ -1428,6 +1428,113 @@ tester.run('no-unused-properties', rule, { } `, options: deepDataOptions + }, + + // ignore public members + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['data'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['computed'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['methods'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['setup'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['props', 'data', 'computed', 'methods', 'setup'], + ignorePublicMembers: true + } + ] } ], @@ -2194,6 +2301,105 @@ tester.run('no-unused-properties', rule, { column: 17 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['props', 'data', 'computed', 'methods', 'setup'], + ignorePublicMembers: true, + deepData: true + } + ], + errors: [ + "'_a' of property found, but never used.", + "'a' of property found, but never used.", + "'_b' of property returned from `setup()` found, but never used.", + "'c._c2' of data found, but never used.", + "'_c' of data found, but never used.", + "'_d' of computed property found, but never used.", + "'_e' of method found, but never used.", + "'_f' of method found, but never used.", + "'_g' of method found, but never used." + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['data'], + ignorePublicMembers: true, + deepData: true + } + ], + errors: [ + "'a' of data found, but never used.", + "'b' of data found, but never used." + ] } ] }) diff --git a/typings/eslint-utils/index.d.ts b/typings/eslint-utils/index.d.ts index 884fc60cd..a97522876 100644 --- a/typings/eslint-utils/index.d.ts +++ b/typings/eslint-utils/index.d.ts @@ -1,4 +1,5 @@ import * as VAST from '../eslint-plugin-vue/util-types/ast' +import { Token, Comment } from '../eslint-plugin-vue/util-types/node' import { ParserServices } from '../eslint-plugin-vue/util-types/parser-services' import eslint from 'eslint' @@ -70,3 +71,5 @@ export namespace ReferenceTracker { const CONSTRUCT: unique symbol const ESM: unique symbol } + +export function isCommentToken(token: Token): token is Comment