eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
1,108 lines (1,042 loc) • 32.3 kB
JavaScript
/**
* @fileoverview Disallow unused properties, data and computed properties.
* @author Learning Equality
*/
'use strict'
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
const utils = require('../utils')
const eslintUtils = require('eslint-utils')
const { getStyleVariablesContext } = require('../utils/style-variables')
/**
* @typedef {import('../utils').GroupName} GroupName
* @typedef {import('../utils').VueObjectData} VueObjectData
*/
/**
* @typedef {object} ComponentObjectPropertyData
* @property {string} name
* @property {GroupName} groupName
* @property {'object'} type
* @property {ASTNode} node
* @property {Property} property
*
* @typedef {object} ComponentNonObjectPropertyData
* @property {string} name
* @property {GroupName} groupName
* @property {'array' | 'type'} type
* @property {ASTNode} node
*
* @typedef { ComponentNonObjectPropertyData | ComponentObjectPropertyData } ComponentPropertyData
*/
/**
* @typedef {object} TemplatePropertiesContainer
* @property {UsedProperties} usedProperties
* @property {Set<string>} refNames
* @typedef {object} VueComponentPropertiesContainer
* @property {ComponentPropertyData[]} properties
* @property {UsedProperties} usedProperties
* @property {UsedProperties} usedPropertiesForProps
*/
// ------------------------------------------------------------------------------
// 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 GROUP_EXPOSE = 'expose'
const PROPERTY_LABEL = {
props: 'property',
data: 'data',
computed: 'computed property',
methods: 'method',
setup: 'property returned from `setup()`',
// not use
watch: 'watch',
provide: 'provide',
inject: 'inject',
expose: 'expose'
}
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
/**
* Find the variable of a given name.
* @param {RuleContext} context The rule context
* @param {Identifier} node The variable name to find.
* @returns {Variable|null} The found variable or null.
*/
function findVariable(context, node) {
return eslintUtils.findVariable(getScope(context, node), node)
}
/**
* Gets the scope for the current node
* @param {RuleContext} context The rule context
* @param {ESNode} 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
/** @type {ESNode | null} */
let node = currentNode
for (; node; node = /** @type {ESNode | null} */ (node.parent)) {
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.
* @param {VReference[]} references
*/
function getReferences(references) {
return references.filter((ref) => ref.variable == null).map((ref) => ref.id)
}
/**
* @param {RuleContext} context
* @param {Identifier} id
* @returns {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration | null}
*/
function findFunction(context, id) {
const calleeVariable = findVariable(context, id)
if (!calleeVariable) {
return null
}
if (calleeVariable.defs.length === 1) {
const def = calleeVariable.defs[0]
if (def.node.type === 'FunctionDeclaration') {
return def.node
}
if (
def.type === 'Variable' &&
def.parent.kind === 'const' &&
def.node.init
) {
if (
def.node.init.type === 'FunctionExpression' ||
def.node.init.type === 'ArrowFunctionExpression'
) {
return def.node.init
}
if (def.node.init.type === 'Identifier') {
return findFunction(context, def.node.init)
}
}
}
return null
}
/**
* @param {RuleContext} context
* @param {Identifier} id
* @returns {Expression}
*/
function findExpression(context, id) {
const variable = findVariable(context, id)
if (!variable) {
return id
}
if (variable.defs.length === 1) {
const def = variable.defs[0]
if (
def.type === 'Variable' &&
def.parent.kind === 'const' &&
def.node.init
) {
if (def.node.init.type === 'Identifier') {
return findExpression(context, def.node.init)
}
return def.node.init
}
}
return id
}
/**
* @typedef { (context: RuleContext) => UsedProperties } UsedPropertiesTracker
* @typedef { { node: CallExpression, index: number } } CallAndParamIndex
*/
/**
* Collects the used property names.
*/
class UsedProperties {
/**
* @param {object} [option]
* @param {boolean} [option.unknown]
*/
constructor(option) {
/** @type {Record<string, UsedPropertiesTracker[]>} */
this.map = Object.create(null)
/** @type {CallAndParamIndex[]} */
this.calls = []
this.unknown = (option && option.unknown) || false
}
/**
* @param {string} name
*/
isUsed(name) {
if (this.unknown) {
// If it is unknown, it is considered used.
return true
}
return Boolean(this.map[name])
}
/**
* @param {string} name
* @param {UsedPropertiesTracker | null} tracker
*/
addUsed(name, tracker) {
const trackers = this.map[name] || (this.map[name] = [])
if (tracker) trackers.push(tracker)
}
/**
* @param {string} name
* @returns {UsedPropertiesTracker}
*/
getPropsTracker(name) {
if (this.unknown) {
return () => new UsedProperties({ unknown: true })
}
const trackers = this.map[name] || []
return (context) => {
const result = new UsedProperties()
for (const tracker of trackers) {
result.merge(tracker(context))
}
return result
}
}
/**
* @param {UsedProperties | null} other
*/
merge(other) {
if (!other) {
return
}
this.unknown = this.unknown || other.unknown
if (this.unknown) {
return
}
for (const [name, otherTrackers] of Object.entries(other.map)) {
const trackers = this.map[name] || (this.map[name] = [])
trackers.push(...otherTrackers)
}
this.calls.push(...other.calls)
}
}
/**
* Collects the used property names for parameters of the function.
*/
class ParamsUsedProperties {
/**
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
* @param {RuleContext} context
*/
constructor(node, context) {
this.node = node
this.context = context
/** @type {UsedProperties[]} */
this.params = []
}
/**
* @param {number} index
* @returns {UsedProperties | null}
*/
getParam(index) {
const param = this.params[index]
if (param != null) {
return param
}
if (this.node.params[index]) {
return (this.params[index] = extractParamOrVerProperties(
this.node.params[index],
this.context
))
}
return null
}
}
/**
* Extract the used property name from one parameter of the function.
* @param {Pattern} node
* @param {RuleContext} context
* @returns {UsedProperties}
*/
function extractParamOrVerProperties(node, context) {
const result = new UsedProperties()
while (node.type === 'AssignmentPattern') {
node = node.left
}
if (node.type === 'RestElement' || node.type === 'ArrayPattern') {
// cannot check
return result
}
if (node.type === 'ObjectPattern') {
result.merge(extractObjectPatternProperties(node))
return result
}
if (node.type !== 'Identifier') {
return result
}
const variable = findVariable(context, node)
if (!variable) {
return result
}
for (const reference of variable.references) {
const id = reference.identifier
result.merge(extractPatternOrThisProperties(id, context, false))
}
return result
}
/**
* Extract the used property name from ObjectPattern.
* @param {ObjectPattern} node
* @returns {UsedProperties}
*/
function extractObjectPatternProperties(node) {
const result = new UsedProperties()
for (const prop of node.properties) {
if (prop.type === 'Property') {
const name = utils.getStaticPropertyName(prop)
if (name) {
result.addUsed(name, getObjectPatternPropertyPatternTracker(prop.value))
} else {
// If cannot trace name, everything is used!
result.unknown = true
return result
}
} else {
// If use RestElement, everything is used!
result.unknown = true
return result
}
}
return result
}
/**
* Extract the used property name from id.
* @param {Identifier} node
* @param {RuleContext} context
* @returns {UsedProperties}
*/
function extractIdentifierProperties(node, context) {
const result = new UsedProperties()
const variable = findVariable(context, node)
if (!variable) {
return result
}
for (const reference of variable.references) {
const id = reference.identifier
result.merge(extractPatternOrThisProperties(id, context, false))
}
return result
}
/**
* Extract the used property name from pattern or `this`.
* @param {Identifier | MemberExpression | ChainExpression | ThisExpression} node
* @param {RuleContext} context
* @param {boolean} withInTemplate
* @returns {UsedProperties}
*/
function extractPatternOrThisProperties(node, context, withInTemplate) {
const result = new UsedProperties()
const parent = node.parent
if (parent.type === 'AssignmentExpression') {
if (withInTemplate) {
return result
}
if (parent.right === node && parent.left.type === 'ObjectPattern') {
// `({foo} = arg)`
result.merge(extractObjectPatternProperties(parent.left))
}
return result
} else if (parent.type === 'VariableDeclarator') {
if (withInTemplate) {
return result
}
if (parent.init === node) {
if (parent.id.type === 'ObjectPattern') {
// `const {foo} = arg`
result.merge(extractObjectPatternProperties(parent.id))
} else if (parent.id.type === 'Identifier') {
// `const foo = arg`
result.merge(extractIdentifierProperties(parent.id, context))
}
}
return result
} else if (parent.type === 'MemberExpression') {
if (parent.object === node) {
// `arg.foo`
const name = utils.getStaticPropertyName(parent)
if (name) {
result.addUsed(name, () =>
extractPatternOrThisProperties(parent, context, withInTemplate)
)
} else {
result.unknown = true
}
}
return result
} else if (parent.type === 'CallExpression') {
if (withInTemplate) {
return result
}
const argIndex = parent.arguments.indexOf(node)
if (argIndex > -1) {
// `foo(arg)`
result.calls.push({
node: parent,
index: argIndex
})
}
} else if (parent.type === 'ChainExpression') {
result.merge(
extractPatternOrThisProperties(parent, context, withInTemplate)
)
} else if (
parent.type === 'ArrowFunctionExpression' ||
parent.type === 'ReturnStatement' ||
parent.type === 'VExpressionContainer' ||
parent.type === 'Property' ||
parent.type === 'ArrayExpression'
) {
// Maybe used externally.
if (maybeExternalUsed(parent)) {
result.unknown = true
}
}
return result
/**
* @param {ASTNode} parentTarget
* @returns {boolean}
*/
function maybeExternalUsed(parentTarget) {
if (
parentTarget.type === 'ReturnStatement' ||
parentTarget.type === 'VExpressionContainer'
) {
return true
}
if (parentTarget.type === 'ArrayExpression') {
return maybeExternalUsed(parentTarget.parent)
}
if (parentTarget.type === 'Property') {
return maybeExternalUsed(parentTarget.parent.parent)
}
if (parentTarget.type === 'ArrowFunctionExpression') {
return parentTarget.body === node
}
return false
}
}
/**
* @param {Pattern} pattern
* @returns {UsedPropertiesTracker}
*/
function getObjectPatternPropertyPatternTracker(pattern) {
if (pattern.type === 'ObjectPattern') {
return () => extractObjectPatternProperties(pattern)
}
if (pattern.type === 'Identifier') {
return (context) => extractIdentifierProperties(pattern, context)
} else if (pattern.type === 'AssignmentPattern') {
return getObjectPatternPropertyPatternTracker(pattern.left)
}
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|\*)\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
// ------------------------------------------------------------------------------
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
},
deepData: { type: 'boolean' },
ignorePublicMembers: { type: 'boolean' }
},
additionalProperties: false
}
],
messages: {
unused: "'{{name}}' of {{group}} found, but never used."
}
},
/** @param {RuleContext} context */
create(context) {
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<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression, ParamsUsedProperties>} */
const paramsUsedPropertiesMap = new Map()
/** @type {TemplatePropertiesContainer} */
const templatePropertiesContainer = {
usedProperties: new UsedProperties(),
refNames: new Set()
}
/** @type {Map<ASTNode, VueComponentPropertiesContainer>} */
const vueComponentPropertiesContainerMap = new Map()
/**
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
* @returns {ParamsUsedProperties}
*/
function getParamsUsedProperties(node) {
let usedProps = paramsUsedPropertiesMap.get(node)
if (!usedProps) {
usedProps = new ParamsUsedProperties(node, context)
paramsUsedPropertiesMap.set(node, usedProps)
}
return usedProps
}
/**
* @param {ASTNode} node
* @returns {VueComponentPropertiesContainer}
*/
function getVueComponentPropertiesContainer(node) {
let container = vueComponentPropertiesContainerMap.get(node)
if (!container) {
container = {
properties: [],
usedProperties: new UsedProperties(),
usedPropertiesForProps: new UsedProperties()
}
vueComponentPropertiesContainerMap.set(node, container)
}
return container
}
/**
* @param {string[]} segments
* @param {Expression} propertyValue
* @param {UsedProperties} baseUsedProperties
*/
function verifyDataOptionDeepProperties(
segments,
propertyValue,
baseUsedProperties
) {
let targetExpr = propertyValue
if (targetExpr.type === 'Identifier') {
targetExpr = findExpression(context, targetExpr)
}
if (targetExpr.type === 'ObjectExpression') {
const usedProperties = resolvedUsedProperties(baseUsedProperties, {
allowUnknownCall: true
})
if (usedProperties.unknown) {
return
}
for (const prop of targetExpr.properties) {
if (prop.type !== 'Property') {
continue
}
const name = utils.getStaticPropertyName(prop)
if (name == null) {
continue
}
if (!usedProperties.isUsed(name)) {
// report
context.report({
node: prop.key,
messageId: 'unused',
data: {
group: PROPERTY_LABEL.data,
name: [...segments, name].join('.')
}
})
continue
}
// next
verifyDataOptionDeepProperties(
[...segments, name],
prop.value,
usedProperties.getPropsTracker(name)(context)
)
}
}
}
/**
* Report all unused properties.
*/
function reportUnusedProperties() {
for (const container of vueComponentPropertiesContainerMap.values()) {
const usedProperties = resolvedUsedProperties(container.usedProperties)
usedProperties.merge(templatePropertiesContainer.usedProperties)
if (usedProperties.unknown) {
continue
}
const usedPropertiesForProps = resolvedUsedProperties(
container.usedPropertiesForProps
)
for (const property of container.properties) {
if (
property.groupName === 'props' &&
usedPropertiesForProps.isUsed(property.name)
) {
// used props
continue
}
if (
property.groupName === 'setup' &&
templatePropertiesContainer.refNames.has(property.name)
) {
// used template refs
continue
}
if (
ignorePublicMembers &&
isPublicMember(property, context.getSourceCode())
) {
continue
}
if (usedProperties.isUsed(property.name)) {
// used
if (
deepData &&
property.groupName === 'data' &&
property.type === 'object'
) {
// Check the deep properties of the data option.
verifyDataOptionDeepProperties(
[property.name],
property.property.value,
usedProperties.getPropsTracker(property.name)(context)
)
}
continue
}
context.report({
node: property.node,
messageId: 'unused',
data: {
group: PROPERTY_LABEL[property.groupName],
name: property.name
}
})
}
}
}
/**
* @param {UsedProperties | null} usedProps
* @param {object} [options]
* @param {boolean} [options.allowUnknownCall]
* @returns {UsedProperties}
*/
function resolvedUsedProperties(usedProps, options) {
const allowUnknownCall = options && options.allowUnknownCall
const already = new Map()
const result = new UsedProperties()
for (const up of iterate(usedProps)) {
result.merge(up)
if (result.unknown) {
break
}
}
return result
/**
* @param {UsedProperties | null} usedProps
* @returns {IterableIterator<UsedProperties>}
*/
function* iterate(usedProps) {
if (!usedProps) {
return
}
yield usedProps
for (const call of usedProps.calls) {
if (call.node.callee.type !== 'Identifier') {
if (allowUnknownCall) {
yield new UsedProperties({ unknown: true })
}
continue
}
const fnNode = findFunction(context, call.node.callee)
if (!fnNode) {
if (allowUnknownCall) {
yield new UsedProperties({ unknown: true })
}
continue
}
let alreadyIndexes = already.get(fnNode)
if (!alreadyIndexes) {
alreadyIndexes = new Set()
already.set(fnNode, alreadyIndexes)
}
if (alreadyIndexes.has(call.index)) {
continue
}
alreadyIndexes.add(call.index)
const paramsUsedProps = getParamsUsedProperties(fnNode)
const paramUsedProps = paramsUsedProps.getParam(call.index)
yield* iterate(paramUsedProps)
}
}
}
/**
* @param {Expression} node
* @returns {Property|null}
*/
function getParentProperty(node) {
if (
!node.parent ||
node.parent.type !== 'Property' ||
node.parent.value !== node
) {
return null
}
const property = node.parent
if (!utils.isProperty(property)) {
return null
}
return property
}
const scriptVisitor = utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
if (!groups.has('props')) {
return
}
const container = getVueComponentPropertiesContainer(node)
for (const prop of props) {
if (!prop.propName) {
continue
}
if (prop.type === 'object') {
container.properties.push({
type: prop.type,
name: prop.propName,
groupName: 'props',
node: prop.key,
property: prop.node
})
} else {
container.properties.push({
type: prop.type,
name: prop.propName,
groupName: 'props',
node: prop.key
})
}
}
let target = node
if (
target.parent &&
target.parent.type === 'CallExpression' &&
target.parent.arguments[0] === target &&
target.parent.callee.type === 'Identifier' &&
target.parent.callee.name === 'withDefaults'
) {
target = target.parent
}
if (
!target.parent ||
target.parent.type !== 'VariableDeclarator' ||
target.parent.init !== target
) {
return
}
const pattern = target.parent.id
const usedProps = extractParamOrVerProperties(pattern, context)
container.usedPropertiesForProps.merge(usedProps)
}
}),
utils.defineVueVisitor(context, {
onVueObjectEnter(node) {
const container = getVueComponentPropertiesContainer(node)
for (const watcherOrExpose of utils.iterateProperties(
node,
new Set([GROUP_WATCHER, GROUP_EXPOSE])
)) {
if (watcherOrExpose.groupName === GROUP_WATCHER) {
const watcher = watcherOrExpose
// Process `watch: { foo /* <- this */ () {} }`
const segments = watcher.name.split('.')
container.usedProperties.addUsed(segments[0], (context) => {
return buildChainTracker(segments)(context)
/**
* @param {string[]} baseSegments
* @returns {UsedPropertiesTracker}
*/
function buildChainTracker(baseSegments) {
return () => {
const subSegments = baseSegments.slice(1)
const usedProps = new UsedProperties()
if (subSegments.length) {
usedProps.addUsed(
subSegments[0],
buildChainTracker(subSegments)
)
}
return usedProps
}
}
})
// Process `watch: { x: 'foo' /* <- this */ }`
if (watcher.type === 'object') {
const property = watcher.property
if (property.kind === 'init') {
for (const handlerValueNode of utils.iterateWatchHandlerValues(
property
)) {
if (
handlerValueNode.type === 'Literal' ||
handlerValueNode.type === 'TemplateLiteral'
) {
const name = utils.getStringLiteralValue(handlerValueNode)
if (name != null) {
container.usedProperties.addUsed(name, null)
}
}
}
}
}
} else if (watcherOrExpose.groupName === GROUP_EXPOSE) {
const expose = watcherOrExpose
container.usedProperties.addUsed(expose.name, null)
}
}
container.properties.push(...utils.iterateProperties(node, groups))
},
/** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property }} node */
'ObjectExpression > Property > :function[params.length>0]'(
node,
vueData
) {
const property = getParentProperty(node)
if (!property) {
return
}
if (property.parent === vueData.node) {
if (utils.getStaticPropertyName(property) !== 'data') {
return
}
// check { data: (vm) => vm.prop }
} else {
const parentProperty = getParentProperty(property.parent)
if (!parentProperty) {
return
}
if (parentProperty.parent === vueData.node) {
if (utils.getStaticPropertyName(parentProperty) !== 'computed') {
return
}
// check { computed: { foo: (vm) => vm.prop } }
} else {
const parentParentProperty = getParentProperty(
parentProperty.parent
)
if (!parentParentProperty) {
return
}
if (parentParentProperty.parent === vueData.node) {
if (
utils.getStaticPropertyName(parentParentProperty) !==
'computed' ||
utils.getStaticPropertyName(property) !== 'get'
) {
return
}
// check { computed: { foo: { get: (vm) => vm.prop } } }
} else {
return
}
}
}
const paramsUsedProps = getParamsUsedProperties(node)
const usedProps = paramsUsedProps.getParam(0)
const container = getVueComponentPropertiesContainer(vueData.node)
container.usedProperties.merge(usedProps)
},
onSetupFunctionEnter(node, vueData) {
const container = getVueComponentPropertiesContainer(vueData.node)
const paramsUsedProps = getParamsUsedProperties(node)
const paramUsedProps = paramsUsedProps.getParam(0)
container.usedPropertiesForProps.merge(paramUsedProps)
},
onRenderFunctionEnter(node, vueData) {
const container = getVueComponentPropertiesContainer(vueData.node)
// Check for Vue 3.x render
const paramsUsedProps = getParamsUsedProperties(node)
const ctxUsedProps = paramsUsedProps.getParam(0)
container.usedPropertiesForProps.merge(ctxUsedProps)
if (container.usedPropertiesForProps.unknown) {
return
}
if (vueData.functional) {
// Check for Vue 2.x render & functional
const contextUsedProps = resolvedUsedProperties(
paramsUsedProps.getParam(1)
)
const tracker = contextUsedProps.getPropsTracker('props')
const propUsedProps = tracker(context)
container.usedPropertiesForProps.merge(propUsedProps)
}
},
/**
* @param {ThisExpression | Identifier} node
* @param {VueObjectData} vueData
*/
'ThisExpression, Identifier'(node, vueData) {
if (!utils.isThis(node, context)) {
return
}
const container = getVueComponentPropertiesContainer(vueData.node)
const usedProps = extractPatternOrThisProperties(node, context, false)
container.usedProperties.merge(usedProps)
}
}),
{
/** @param {Program} node */
'Program:exit'(node) {
const styleVars = getStyleVariablesContext(context)
if (styleVars) {
for (const { id } of styleVars.references) {
templatePropertiesContainer.usedProperties.addUsed(
id.name,
(context) => extractPatternOrThisProperties(id, context, true)
)
}
}
if (!node.templateBody) {
reportUnusedProperties()
}
}
}
)
const templateVisitor = {
/**
* @param {VExpressionContainer} node
*/
VExpressionContainer(node) {
for (const id of getReferences(node.references)) {
templatePropertiesContainer.usedProperties.addUsed(
id.name,
(context) => extractPatternOrThisProperties(id, context, true)
)
}
},
/**
* @param {VAttribute} node
*/
'VAttribute[directive=false]'(node) {
if (node.key.name === 'ref' && node.value != null) {
templatePropertiesContainer.refNames.add(node.value.value)
}
},
"VElement[parent.type!='VElement']:exit"() {
reportUnusedProperties()
}
}
return utils.defineTemplateBodyVisitor(
context,
templateVisitor,
scriptVisitor
)
}
}