diff --git a/docs/rules/README.md b/docs/rules/README.md
index 2b7defe57..e160ba03d 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -295,6 +295,7 @@ For example:
| [vue/no-template-target-blank](./no-template-target-blank.md) | disallow target="_blank" attribute without rel="noopener noreferrer" | |
| [vue/no-unregistered-components](./no-unregistered-components.md) | disallow using components that are not registered inside templates | |
| [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
+| [vue/no-unused-properties](./no-unused-properties.md) | disallow unused properties | |
| [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
| [vue/prefer-template](./prefer-template.md) | require template literals instead of string concatenation | :wrench: |
diff --git a/docs/rules/no-unused-properties.md b/docs/rules/no-unused-properties.md
new file mode 100644
index 000000000..438187060
--- /dev/null
+++ b/docs/rules/no-unused-properties.md
@@ -0,0 +1,164 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-unused-properties
+description: disallow unused properties
+---
+# vue/no-unused-properties
+> disallow unused properties
+
+## :book: Rule Details
+
+This rule is aimed at eliminating unused properties.
+
+::: warning Note
+This rule cannot be checked for use in other components (e.g. `mixins`, Property access via `$refs`) and use in places where the scope cannot be determined.
+:::
+
+
+
+```vue
+
+
+ {{ count }}
+
+
+```
+
+
+
+
+
+```vue
+
+
+ {{ cnt }}
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-unused-properties": ["error", {
+ "groups": ["props"]
+ }]
+}
+```
+
+- `"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"`
+
+### `"groups": ["props", "data"]`
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+### `"groups": ["props", "computed"]`
+
+
+
+```vue
+
+
+ {{ reversedMessage }}
+
+
+```
+
+
+
+
+
+```vue
+
+
+ {{ message }}
+
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unused-properties.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unused-properties.js)
diff --git a/lib/index.js b/lib/index.js
index 7b3b5d9d1..ba8da3e72 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -84,6 +84,7 @@ module.exports = {
'no-unregistered-components': require('./rules/no-unregistered-components'),
'no-unsupported-features': require('./rules/no-unsupported-features'),
'no-unused-components': require('./rules/no-unused-components'),
+ 'no-unused-properties': require('./rules/no-unused-properties'),
'no-unused-vars': require('./rules/no-unused-vars'),
'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),
'no-v-html': require('./rules/no-v-html'),
diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js
new file mode 100644
index 000000000..2462446e4
--- /dev/null
+++ b/lib/rules/no-unused-properties.js
@@ -0,0 +1,498 @@
+/**
+ * @fileoverview Disallow unused properties, data and computed properties.
+ * @author Learning Equality
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+const eslintUtils = require('eslint-utils')
+
+/**
+ * @typedef {import('vue-eslint-parser').AST.Node} Node
+ * @typedef {import('vue-eslint-parser').AST.ESLintNode} ASTNode
+ * @typedef {import('vue-eslint-parser').AST.ESLintObjectPattern} ObjectPattern
+ * @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier
+ * @typedef {import('vue-eslint-parser').AST.ESLintThisExpression} ThisExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintFunctionExpression} FunctionExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintArrowFunctionExpression} ArrowFunctionExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintFunctionDeclaration} FunctionDeclaration
+ * @typedef {import('eslint').Scope.Variable} Variable
+ * @typedef {import('eslint').Rule.RuleContext} RuleContext
+ */
+/**
+ * @typedef { { name: string, groupName: string, node: ASTNode } } PropertyData
+ * @typedef { { usedNames: Set } } TemplatePropertiesContainer
+ * @typedef { { properties: Array, usedNames: Set, unknown: boolean, usedPropsNames: Set, unknownProps: boolean } } VueComponentPropertiesContainer
+ * @typedef { { node: FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, index: number } } CallIdAndParamIndex
+ * @typedef { { usedNames: Set, unknown: boolean } } UsedProperties
+ */
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const GROUP_PROPERTY = 'props'
+const GROUP_DATA = 'data'
+const GROUP_COMPUTED_PROPERTY = 'computed'
+const GROUP_METHODS = 'methods'
+const GROUP_SETUP = 'setup'
+const GROUP_WATCHER = 'watch'
+
+const PROPERTY_LABEL = {
+ [GROUP_PROPERTY]: 'property',
+ [GROUP_DATA]: 'data',
+ [GROUP_COMPUTED_PROPERTY]: 'computed property',
+ [GROUP_METHODS]: 'method',
+ [GROUP_SETUP]: 'property returned from `setup()`'
+}
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Find the variable of a given name.
+ * @param {RuleContext} context The rule context
+ * @param {ASTNode} node The variable name to find.
+ * @returns {Variable|null} The found variable or null.
+ */
+function findVariable (context, node) {
+ // @ts-ignore
+ return eslintUtils.findVariable(getScope(context, node), node)
+}
+/**
+ * Gets the scope for the current node
+ * @param {RuleContext} context The rule context
+ * @param {ASTNode} currentNode The node to get the scope of
+ * @returns { import('eslint-scope').Scope } The scope information for this node
+ */
+function getScope (context, currentNode) {
+ // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
+ const inner = currentNode.type !== 'Program'
+ const scopeManager = context.getSourceCode().scopeManager
+
+ // @ts-ignore
+ for (let node = currentNode; node; node = node.parent) {
+ // @ts-ignore
+ const scope = scopeManager.acquire(node, inner)
+
+ if (scope) {
+ if (scope.type === 'function-expression-name') {
+ return scope.childScopes[0]
+ }
+ return scope
+ }
+ }
+
+ return scopeManager.scopes[0]
+}
+
+/**
+ * Extract names from references objects.
+ */
+function getReferencesNames (references) {
+ return references
+ .filter(ref => ref.variable == null)
+ .map(ref => ref.id.name)
+}
+
+/**
+ * @param {ObjectPattern} node
+ * @returns {UsedProperties}
+ */
+function extractObjectPatternProperties (node) {
+ const usedNames = new Set()
+ for (const prop of node.properties) {
+ if (prop.type === 'Property') {
+ usedNames.add(utils.getStaticPropertyName(prop))
+ } else {
+ // If use RestElement, everything is used!
+ return {
+ usedNames,
+ unknown: true
+ }
+ }
+ }
+ return {
+ usedNames,
+ unknown: false
+ }
+}
+
+/**
+ * @param {Identifier | ThisExpression} node
+ * @param {RuleContext} context
+ * @returns {UsedProps}
+ */
+function extractIdOrThisProperties (node, context) {
+ /** @type {UsedProps} */
+ const result = new UsedProps()
+ const parent = node.parent
+ if (parent.type === 'AssignmentExpression') {
+ if (parent.right === node && parent.left.type === 'ObjectPattern') {
+ // `({foo} = arg)`
+ const { usedNames, unknown } = extractObjectPatternProperties(parent.left)
+ usedNames.forEach(name => result.usedNames.add(name))
+ result.unknown = result.unknown || unknown
+ }
+ } else if (parent.type === 'VariableDeclarator') {
+ if (parent.init === node && parent.id.type === 'ObjectPattern') {
+ // `const {foo} = arg`
+ const { usedNames, unknown } = extractObjectPatternProperties(parent.id)
+ usedNames.forEach(name => result.usedNames.add(name))
+ result.unknown = result.unknown || unknown
+ }
+ } else if (parent.type === 'MemberExpression') {
+ if (parent.object === node) {
+ // `arg.foo`
+ const name = utils.getStaticPropertyName(parent)
+ if (name) {
+ result.usedNames.add(name)
+ } else {
+ result.unknown = true
+ }
+ }
+ } else if (parent.type === 'CallExpression') {
+ const argIndex = parent.arguments.indexOf(node)
+ if (argIndex > -1 && parent.callee.type === 'Identifier') {
+ // `foo(arg)`
+ const calleeVariable = findVariable(context, parent.callee)
+ if (!calleeVariable) {
+ return result
+ }
+ if (calleeVariable.defs.length === 1) {
+ const def = calleeVariable.defs[0]
+ if (
+ def.type === 'Variable' &&
+ def.parent &&
+ def.parent.kind === 'const' &&
+ (def.node.init.type === 'FunctionExpression' || def.node.init.type === 'ArrowFunctionExpression')
+ ) {
+ result.calls.push({
+ // @ts-ignore
+ node: def.node.init,
+ index: argIndex
+ })
+ } else if (def.node.type === 'FunctionDeclaration') {
+ result.calls.push({
+ node: def.node,
+ index: argIndex
+ })
+ }
+ }
+ }
+ }
+ return result
+}
+
+/**
+ * Collects the property names used.
+ */
+class UsedProps {
+ constructor () {
+ /** @type {Set} */
+ this.usedNames = new Set()
+ /** @type {CallIdAndParamIndex[]} */
+ this.calls = []
+ this.unknown = false
+ }
+}
+
+/**
+ * Collects the property names used for one parameter of the function.
+ */
+class ParamUsedProps extends UsedProps {
+ /**
+ * @param {ASTNode} paramNode
+ * @param {RuleContext} context
+ */
+ constructor (paramNode, context) {
+ super()
+
+ if (paramNode.type === 'RestElement' || paramNode.type === 'ArrayPattern') {
+ // cannot check
+ return
+ }
+ if (paramNode.type === 'ObjectPattern') {
+ const { usedNames, unknown } = extractObjectPatternProperties(paramNode)
+ usedNames.forEach(name => this.usedNames.add(name))
+ this.unknown = this.unknown || unknown
+ return
+ }
+ const variable = findVariable(context, paramNode)
+ if (!variable) {
+ return
+ }
+ for (const reference of variable.references) {
+ /** @type {Identifier} */
+ // @ts-ignore
+ const id = reference.identifier
+ const { usedNames, unknown, calls } = extractIdOrThisProperties(id, context)
+ usedNames.forEach(name => this.usedNames.add(name))
+ this.unknown = this.unknown || unknown
+ this.calls.push(...calls)
+ }
+ }
+}
+
+/**
+ * Collects the property names used for parameters of the function.
+ */
+class ParamsUsedProps {
+ /**
+ * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
+ * @param {RuleContext} context
+ */
+ constructor (node, context) {
+ this.node = node
+ this.context = context
+ /** @type {ParamUsedProps[]} */
+ this.params = []
+ }
+
+ /**
+ * @param {number} index
+ * @returns {ParamUsedProps}
+ */
+ getParam (index) {
+ const param = this.params[index]
+ if (param != null) {
+ return param
+ }
+ if (this.node.params[index]) {
+ return (this.params[index] = new ParamUsedProps(this.node.params[index], this.context))
+ }
+ return null
+ }
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow unused properties',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-unused-properties.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ groups: {
+ type: 'array',
+ items: {
+ enum: [
+ GROUP_PROPERTY,
+ GROUP_DATA,
+ GROUP_COMPUTED_PROPERTY,
+ GROUP_METHODS,
+ GROUP_SETUP
+ ]
+ },
+ additionalItems: false,
+ uniqueItems: true
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ unused: "'{{name}}' of {{group}} found, but never used."
+ }
+ },
+
+ create (context) {
+ const options = context.options[0] || {}
+ const groups = new Set(options.groups || [GROUP_PROPERTY])
+
+ /** @type {Map} */
+ const paramsUsedPropsMap = new Map()
+ /** @type {TemplatePropertiesContainer} */
+ const templatePropertiesContainer = {
+ usedNames: new Set()
+ }
+ /** @type {Map} */
+ const vueComponentPropertiesContainerMap = new Map()
+
+ /**
+ * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
+ * @returns {ParamsUsedProps}
+ */
+ function getParamsUsedProps (node) {
+ let usedProps = paramsUsedPropsMap.get(node)
+ if (!usedProps) {
+ usedProps = new ParamsUsedProps(node, context)
+ paramsUsedPropsMap.set(node, usedProps)
+ }
+ return usedProps
+ }
+
+ /**
+ * @param {ASTNode} node
+ * @returns {VueComponentPropertiesContainer}
+ */
+ function getVueComponentPropertiesContainer (node) {
+ let container = vueComponentPropertiesContainerMap.get(node)
+ if (!container) {
+ container = {
+ properties: [],
+ usedNames: new Set(),
+ usedPropsNames: new Set(),
+ unknown: false,
+ unknownProps: false
+ }
+ vueComponentPropertiesContainerMap.set(node, container)
+ }
+ return container
+ }
+
+ /**
+ * Report all unused properties.
+ */
+ function reportUnusedProperties () {
+ for (const container of vueComponentPropertiesContainerMap.values()) {
+ if (container.unknown) {
+ continue
+ }
+ for (const property of container.properties) {
+ if (container.usedNames.has(property.name) || templatePropertiesContainer.usedNames.has(property.name)) {
+ continue
+ }
+ if (property.groupName === 'props' && (container.unknownProps || container.usedPropsNames.has(property.name))) {
+ continue
+ }
+ context.report({
+ node: property.node,
+ messageId: 'unused',
+ data: {
+ group: PROPERTY_LABEL[property.groupName],
+ name: property.name
+ }
+ })
+ }
+ }
+ }
+
+ /**
+ * @param {UsedProps} usedProps
+ * @param {Map>} already
+ * @returns {Generator}
+ */
+ function * iterateUsedProps (usedProps, already = new Map()) {
+ yield usedProps
+ for (const call of usedProps.calls) {
+ let alreadyIndexes = already.get(call.node)
+ if (!alreadyIndexes) {
+ alreadyIndexes = new Set()
+ already.set(call.node, alreadyIndexes)
+ }
+ if (alreadyIndexes.has(call.index)) {
+ continue
+ }
+ alreadyIndexes.add(call.index)
+ const paramsUsedProps = getParamsUsedProps(call.node)
+ const paramUsedProps = paramsUsedProps.getParam(call.index)
+ if (!paramUsedProps) {
+ continue
+ }
+ yield paramUsedProps
+ yield * iterateUsedProps(paramUsedProps, already)
+ }
+ }
+
+ const scriptVisitor = Object.assign(
+ {},
+ utils.defineVueVisitor(context, {
+ ObjectExpression (node, vueData) {
+ if (node !== vueData.node) {
+ return
+ }
+
+ const container = getVueComponentPropertiesContainer(vueData.node)
+ const watcherNames = new Set()
+ for (const watcher of utils.iterateProperties(node, new Set([GROUP_WATCHER]))) {
+ watcherNames.add(watcher.name)
+ }
+ for (const prop of utils.iterateProperties(node, groups)) {
+ if (watcherNames.has(prop.name)) {
+ continue
+ }
+ container.properties.push(prop)
+ }
+ },
+ 'Property[value.type=/^(Arrow)?FunctionExpression$/]' (node, vueData) {
+ if (node.parent !== vueData.node) {
+ return
+ }
+ if (utils.getStaticPropertyName(node) !== 'setup') {
+ return
+ }
+ const container = getVueComponentPropertiesContainer(vueData.node)
+ const propsParam = node.value.params[0]
+ if (!propsParam) {
+ // no arguments
+ return
+ }
+ const paramsUsedProps = getParamsUsedProps(node.value)
+ const paramUsedProps = paramsUsedProps.getParam(0)
+
+ for (const { usedNames, unknown } of iterateUsedProps(paramUsedProps)) {
+ if (unknown) {
+ container.unknownProps = true
+ return
+ }
+ for (const name of usedNames) {
+ container.usedPropsNames.add(name)
+ }
+ }
+ },
+ 'ThisExpression, Identifier' (node, vueData) {
+ if (!utils.isThis(node, context)) {
+ return
+ }
+ const container = getVueComponentPropertiesContainer(vueData.node)
+ const usedProps = extractIdOrThisProperties(node, context)
+
+ for (const { usedNames, unknown } of iterateUsedProps(usedProps)) {
+ if (unknown) {
+ container.unknown = true
+ return
+ }
+ for (const name of usedNames) {
+ container.usedNames.add(name)
+ }
+ }
+ }
+ }),
+ {
+ 'Program:exit' (node) {
+ if (!node.templateBody) {
+ reportUnusedProperties()
+ }
+ }
+ },
+ )
+
+ const templateVisitor = {
+ 'VExpressionContainer' (node) {
+ for (const name of getReferencesNames(node.references)) {
+ templatePropertiesContainer.usedNames.add(name)
+ }
+ },
+ "VElement[parent.type!='VElement']:exit" () {
+ reportUnusedProperties()
+ }
+ }
+
+ return utils.defineTemplateBodyVisitor(context, templateVisitor, scriptVisitor)
+ }
+}
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 296acd87a..ad72908fe 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -966,6 +966,17 @@ module.exports = {
if (node.type !== 'Identifier') {
return false
}
+ const parent = node.parent
+ if (parent.type === 'MemberExpression') {
+ if (parent.property === node) {
+ return false
+ }
+ } else if (parent.type === 'Property') {
+ if (parent.key === node && !parent.computed) {
+ return false
+ }
+ }
+
const variable = findVariable(context.getScope(), node)
if (variable != null && variable.defs.length === 1) {
diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js
new file mode 100644
index 000000000..a17aff8b9
--- /dev/null
+++ b/tests/lib/rules/no-unused-properties.js
@@ -0,0 +1,1245 @@
+/**
+ * @fileoverview Disallow unused properties, data and computed properties.
+ * @author Learning Equality
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-unused-properties')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+const allOptions = [{ groups: ['props', 'computed', 'data', 'methods', 'setup'] }]
+
+tester.run('no-unused-properties', rule, {
+ valid: [
+ // a property used in a script expression
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ // default options
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+
+ // a property being watched
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+
+ // a property used as a template identifier
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ count }}
+
+
+ `
+ },
+
+ // properties used in a template expression
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ count1 + count2 }}
+
+
+ `
+ },
+
+ // a property used in v-if
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+
+ // a property used in v-for
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ color }}
+
+
+ `
+ },
+
+ // a property used in v-html
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+
+ // a property passed in a component
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+
+ // a property used in v-on
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+
+ // data used in a script expression
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: allOptions
+ },
+
+ // data being watched
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: allOptions
+ },
+
+ // data used as a template identifier
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ count }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // data used in a template expression
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ count1 + count2 }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // data used in v-if
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // data used in v-for
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ color }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // data used in v-html
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // data used in v-model
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // data passed in a component
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // data used in v-on
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed property used in a script expression
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: allOptions
+ },
+
+ // computed property being watched
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: allOptions
+ },
+
+ // computed property used as a template identifier
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ count }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed properties used in a template expression
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ count1 + count2 }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed property used in v-if
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed property used in v-for
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ color }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed property used in v-html
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed property used in v-model
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // computed property passed in a component
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: allOptions
+ },
+
+ // ignores unused data when marked with eslint-disable
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ cont }}
+
+
+ `,
+ options: allOptions
+ },
+
+ // trace this
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ // use rest
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ foo }}
+
+
+ `
+ },
+
+ // function trace
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }
+ ],
+
+ invalid: [
+ // unused property
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ cont }}
+
+
+ `,
+ errors: [
+ {
+ message: "'count' of property found, but never used.",
+ line: 7
+ }
+ ]
+ },
+
+ // unused data
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ cont }}
+
+
+ `,
+ options: [{ groups: ['props', 'computed', 'data'] }],
+ errors: [
+ {
+ message: "'count' of data found, but never used.",
+ line: 9
+ }
+ ]
+ },
+
+ // unused computed property
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ cont }}
+
+
+ `,
+ options: [{ groups: ['props', 'computed', 'data'] }],
+ errors: [
+ {
+ message: "'count' of computed property found, but never used.",
+ line: 8
+ }
+ ]
+ },
+
+ // all options
+ {
+ filename: 'test.vue',
+ code: `
+
+ {{ foo }}
+
+
+ `,
+ options: allOptions,
+ errors: [
+ {
+ message: "'a' of property found, but never used.",
+ line: 7
+ },
+ {
+ message: "'b' of data found, but never used.",
+ line: 9
+ },
+ {
+ message: "'c' of computed property found, but never used.",
+ line: 12
+ },
+ {
+ message: "'d' of method found, but never used.",
+ line: 17
+ },
+ {
+ message: "'e' of property returned from `setup()` found, but never used.",
+ line: 20
+ }
+ ]
+ },
+
+ // trace this
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: "'count' of property found, but never used.",
+ line: 4
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: "'count' of property found, but never used.",
+ line: 4
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: "'count' of property found, but never used.",
+ line: 4
+ }
+ ]
+ },
+
+ // setup
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used."
+ ]
+ },
+
+ // function trace
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'baz' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used.",
+ "'baz' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'baz' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used.",
+ "'baz' of property found, but never used."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ "'foo' of property found, but never used.",
+ "'bar' of property found, but never used.",
+ "'baz' of property found, but never used."
+ ]
+ }
+ ]
+})