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