diff --git a/docs/rules/no-undef-properties.md b/docs/rules/no-undef-properties.md
index 5f1312d2b..e7979756a 100644
--- a/docs/rules/no-undef-properties.md
+++ b/docs/rules/no-undef-properties.md
@@ -22,6 +22,29 @@ Note that there are many false positives if you are using mixins.
+```vue
+
+
+ {{ name }}: {{ count }}
+
+ {{ label }}: {{ cnt }}
+
+
+```
+
+
+
+
+
```vue
diff --git a/lib/rules/no-undef-properties.js b/lib/rules/no-undef-properties.js
index 13f00e41c..9634d8086 100644
--- a/lib/rules/no-undef-properties.js
+++ b/lib/rules/no-undef-properties.js
@@ -12,10 +12,14 @@ const utils = require('../utils')
const eslintUtils = require('eslint-utils')
const reserved = require('../utils/vue-reserved.json')
const { toRegExp } = require('../utils/regexp')
+const { getStyleVariablesContext } = require('../utils/style-variables')
+const {
+ definePropertyReferenceExtractor
+} = require('../utils/property-references')
/**
- * @typedef {import('../utils').ComponentPropertyData} ComponentPropertyData
* @typedef {import('../utils').VueObjectData} VueObjectData
+ * @typedef {import('../utils/property-references').IPropertyReferences} IPropertyReferences
*/
/**
* @typedef {object} PropertyData
@@ -28,6 +32,15 @@ const { toRegExp } = require('../utils/regexp')
// Helpers
// ------------------------------------------------------------------------------
+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 GROUP_INJECT = 'inject'
+
/**
* Find the variable of a given name.
* @param {RuleContext} context The rule context
@@ -64,48 +77,6 @@ function getScope(context, currentNode) {
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
-}
-
/**
* @callback ReferencePropertiesTracker
* @param {RuleContext} context
@@ -168,72 +139,6 @@ class ReferencePropertiesImpl {
/** @type { ReferenceProperties } */
const EMPTY_REFS = new ReferencePropertiesImpl()
-/**
- * Collects the property reference names for parameters of the function.
- */
-class ParamsReferenceProperties {
- /**
- * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
- * @param {RuleContext} context
- */
- constructor(node, context) {
- this.node = node
- this.context = context
- /** @type {ReferenceProperties[]} */
- this.params = []
- }
-
- /**
- * @param {number} index
- * @returns {ReferenceProperties | null}
- */
- getParam(index) {
- const param = this.params[index]
- if (param != null) {
- return param
- }
- if (this.node.params[index]) {
- return (this.params[index] = extractParamReferences(
- this.node.params[index],
- this.context
- ))
- }
- return null
- }
-}
-/**
- * Extract the property reference name from one parameter of the function.
- * @param {Pattern} node
- * @param {RuleContext} context
- * @returns {ReferenceProperties}
- */
-function extractParamReferences(node, context) {
- while (node.type === 'AssignmentPattern') {
- node = node.left
- }
- if (node.type === 'RestElement' || node.type === 'ArrayPattern') {
- // cannot check
- return EMPTY_REFS
- }
- if (node.type === 'ObjectPattern') {
- return extractObjectPatternReferences(node)
- }
- if (node.type !== 'Identifier') {
- return EMPTY_REFS
- }
- const variable = findVariable(context, node)
- if (!variable) {
- return EMPTY_REFS
- }
- const result = new ReferencePropertiesImpl()
- for (const reference of variable.references) {
- const id = reference.identifier
- result.merge(extractPatternOrThisReferences(id, context, false))
- }
-
- return result
-}
-
/**
* Extract the property reference name from ObjectPattern.
* @param {ObjectPattern} node
@@ -425,6 +330,8 @@ module.exports = {
const ignores = /** @type {string[]} */ (options.ignores || ['/^\\$/']).map(
toRegExp
)
+ const propertyReferenceExtractor = definePropertyReferenceExtractor(context)
+ const programNode = context.getSourceCode().ast
/** Vue component context */
class VueComponentContext {
@@ -435,66 +342,34 @@ module.exports = {
/** @type { Set } */
this.reported = new Set()
}
-
/**
* Report
- * @param {ASTNode} node
- * @param {string} name
- * @param {'undef' | 'undefProps'} messageId
- */
- report(node, name, messageId = 'undef') {
- if (
- reserved.includes(name) ||
- ignores.some((ignore) => ignore.test(name))
- ) {
- return
- }
- if (
- // Prevents reporting to the same node.
- this.reported.has(node) ||
- // Prevents reports with the same name.
- // This is so that intentional undefined properties can be resolved with
- // a single warning suppression comment (`// eslint-disable-line`).
- this.reported.has(name)
- ) {
- return
- }
- this.reported.add(node)
- this.reported.add(name)
- context.report({
- node,
- messageId,
- data: {
- name
- }
- })
- }
-
- /**
- * Verify reference properties
- * @param {ReferenceProperties | null} refs
+ * @param {IPropertyReferences} references
* @param {object} [options]
* @param {boolean} [options.props]
*/
- verifyUndefProperties(refs, options) {
+ verifyReferences(references, options) {
const that = this
- verifyUndefProperties(this.properties, refs, null)
+ verifyUndefProperties(this.properties, references, null)
/**
* @param { { get: (name: string) => PropertyData | null | undefined } } propData
- * @param {ReferenceProperties|null} refs
+ * @param {IPropertyReferences|null} references
* @param {string|null} pathName
*/
- function verifyUndefProperties(propData, refs, pathName) {
- for (const ref of iterateResolvedRefs(refs)) {
+ function verifyUndefProperties(propData, references, pathName) {
+ if (!references) {
+ return
+ }
+ for (const [refName, { nodes }] of references.allProperties()) {
/** @type {'undef' | 'undefProps' | null} */
let messageId = null
const referencePathName = pathName
- ? `${pathName}.${ref.name}`
- : ref.name
+ ? `${pathName}.${refName}`
+ : refName
- const prop = propData.get(ref.name)
+ const prop = propData.get(refName)
if (prop) {
if (options && options.props) {
if (!prop.isProps) {
@@ -506,7 +381,7 @@ module.exports = {
if (prop.hasNestProperty) {
verifyUndefProperties(
prop,
- ref.tracker(context),
+ references.getNest(refName),
referencePathName
)
}
@@ -515,30 +390,48 @@ module.exports = {
} else {
messageId = 'undef'
}
- that.report(ref.node, referencePathName, messageId)
+ that.report(nodes[0], referencePathName, messageId)
}
}
}
+ /**
+ * Report
+ * @param {ASTNode} node
+ * @param {string} name
+ * @param {'undef' | 'undefProps'} messageId
+ */
+ report(node, name, messageId = 'undef') {
+ if (
+ reserved.includes(name) ||
+ ignores.some((ignore) => ignore.test(name))
+ ) {
+ return
+ }
+ if (
+ // Prevents reporting to the same node.
+ this.reported.has(node) ||
+ // Prevents reports with the same name.
+ // This is so that intentional undefined properties can be resolved with
+ // a single warning suppression comment (`// eslint-disable-line`).
+ this.reported.has(name)
+ ) {
+ return
+ }
+ this.reported.add(node)
+ this.reported.add(name)
+ context.report({
+ node,
+ messageId,
+ data: {
+ name
+ }
+ })
+ }
}
- /** @type {Map} */
- const paramsReferencePropertiesMap = new Map()
/** @type {Map} */
const vueComponentContextMap = new Map()
- /**
- * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
- * @returns {ParamsReferenceProperties}
- */
- function getParamsReferenceProperties(node) {
- let usedProps = paramsReferencePropertiesMap.get(node)
- if (!usedProps) {
- usedProps = new ParamsReferenceProperties(node, context)
- paramsReferencePropertiesMap.set(node, usedProps)
- }
- return usedProps
- }
-
/**
* @param {ASTNode} node
* @returns {VueComponentContext}
@@ -551,47 +444,33 @@ module.exports = {
}
return ctx
}
-
/**
- * @param {ReferenceProperties|null} refs
- * @returns { IterableIterator[ }
+ * @returns {VueComponentContext|void}
*/
- function* iterateResolvedRefs(refs) {
- const already = new Map()
-
- yield* iterate(refs)
+ function getVueComponentContextForTemplate() {
+ const keys = [...vueComponentContextMap.keys()]
+ const exported =
+ keys.find(isScriptSetupProgram) || keys.find(isExportObject)
+ return exported && vueComponentContextMap.get(exported)
/**
- * @param {ReferenceProperties|null} refs
- * @returns {IterableIterator][}
+ * @param {ASTNode} node
+ */
+ function isScriptSetupProgram(node) {
+ return node === programNode
+ }
+ /**
+ * @param {ASTNode} node
*/
- function* iterate(refs) {
- if (!refs) {
- return
- }
- yield* refs.iterateRefs()
- for (const call of refs.calls) {
- if (call.node.callee.type !== 'Identifier') {
- continue
+ function isExportObject(node) {
+ let parent = node.parent
+ while (parent) {
+ if (parent.type === 'ExportDefaultDeclaration') {
+ return true
}
- const fnNode = findFunction(context, call.node.callee)
- if (!fnNode) {
- 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 paramsRefs = getParamsReferenceProperties(fnNode)
- const paramRefs = paramsRefs.getParam(call.index)
- yield* iterate(paramRefs)
+ parent = parent.parent
}
+ return false
}
}
@@ -615,24 +494,98 @@ module.exports = {
}
const scriptVisitor = utils.compositingVisitors(
- {},
+ {
+ /** @param {Program} node */
+ Program() {
+ if (!utils.isScriptSetup(context)) {
+ return
+ }
+
+ const ctx = getVueComponentContext(programNode)
+ const globalScope = context.getSourceCode().scopeManager.globalScope
+ if (globalScope) {
+ for (const variable of globalScope.variables) {
+ ctx.properties.set(variable.name, {
+ hasNestProperty: false,
+ get: () => null
+ })
+ }
+ const moduleScope = globalScope.childScopes.find(
+ (scope) => scope.type === 'module'
+ )
+ for (const variable of (moduleScope && moduleScope.variables) ||
+ []) {
+ ctx.properties.set(variable.name, {
+ hasNestProperty: false,
+ get: () => null
+ })
+ }
+ }
+ }
+ },
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node, props) {
+ const ctx = getVueComponentContext(programNode)
+
+ for (const prop of props) {
+ if (!prop.propName) {
+ continue
+ }
+ ctx.properties.set(prop.propName, {
+ hasNestProperty: false,
+ isProps: true,
+ get: () => null
+ })
+ }
+ 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 propertyReferences =
+ propertyReferenceExtractor.extractFromPattern(pattern)
+ ctx.verifyReferences(propertyReferences)
+ }
+ }),
utils.defineVueVisitor(context, {
onVueObjectEnter(node) {
const ctx = getVueComponentContext(node)
for (const prop of utils.iterateProperties(
node,
- new Set(['props', 'data', 'computed', 'setup', 'methods', 'inject'])
+ new Set([
+ GROUP_PROPERTY,
+ GROUP_DATA,
+ GROUP_COMPUTED_PROPERTY,
+ GROUP_SETUP,
+ GROUP_METHODS,
+ GROUP_INJECT
+ ])
)) {
const propertyMap =
- prop.groupName === 'data' &&
+ prop.groupName === GROUP_DATA &&
prop.type === 'object' &&
prop.property.value.type === 'ObjectExpression'
? getObjectPropertyMap(prop.property.value)
: null
ctx.properties.set(prop.name, {
hasNestProperty: Boolean(propertyMap),
- isProps: prop.groupName === 'props',
+ isProps: prop.groupName === GROUP_PROPERTY,
get(name) {
if (!propertyMap) {
return null
@@ -642,55 +595,42 @@ module.exports = {
})
}
- for (const watcher of utils.iterateProperties(
+ for (const watcherOrExpose of utils.iterateProperties(
node,
- new Set(['watch'])
+ new Set([GROUP_WATCHER, GROUP_EXPOSE])
)) {
- // Process `watch: { foo /* <- this */ () {} }`
- const segments = watcher.name.split('.')
-
- const propData = ctx.properties.get(segments[0])
- if (!propData) {
- ctx.report(watcher.node, segments[0])
- } else {
- let targetPropData = propData
- let index = 1
- while (
- targetPropData.hasNestProperty &&
- index < segments.length
- ) {
- const nestPropData = targetPropData.get(segments[index])
- if (!nestPropData) {
- ctx.report(
- watcher.node,
- segments.slice(0, index + 1).join('.')
- )
- break
- } else {
- index++
- targetPropData = nestPropData
- }
- }
- }
-
- // 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 && !ctx.properties.get(name)) {
- ctx.report(handlerValueNode, name)
- }
+ if (watcherOrExpose.groupName === GROUP_WATCHER) {
+ const watcher = watcherOrExpose
+ // Process `watch: { foo /* <- this */ () {} }`
+ ctx.verifyReferences(
+ propertyReferenceExtractor.extractFromPath(
+ watcher.name,
+ watcher.node
+ )
+ )
+ // Process `watch: { x: 'foo' /* <- this */ }`
+ if (watcher.type === 'object') {
+ const property = watcher.property
+ if (property.kind === 'init') {
+ for (const handlerValueNode of utils.iterateWatchHandlerValues(
+ property
+ )) {
+ ctx.verifyReferences(
+ propertyReferenceExtractor.extractFromNameLiteral(
+ handlerValueNode
+ )
+ )
}
}
}
+ } else if (watcherOrExpose.groupName === GROUP_EXPOSE) {
+ const expose = watcherOrExpose
+ ctx.verifyReferences(
+ propertyReferenceExtractor.extractFromName(
+ expose.name,
+ expose.node
+ )
+ )
}
}
},
@@ -742,16 +682,16 @@ module.exports = {
}
}
- const paramsRefs = getParamsReferenceProperties(node)
- const refs = paramsRefs.getParam(0)
+ const propertyReferences =
+ propertyReferenceExtractor.extractFromFunctionParam(node, 0)
const ctx = getVueComponentContext(vueData.node)
- ctx.verifyUndefProperties(refs, { props })
+ ctx.verifyReferences(propertyReferences, { props })
},
onSetupFunctionEnter(node, vueData) {
- const paramsRefs = getParamsReferenceProperties(node)
- const paramRefs = paramsRefs.getParam(0)
+ const propertyReferences =
+ propertyReferenceExtractor.extractFromFunctionParam(node, 0)
const ctx = getVueComponentContext(vueData.node)
- ctx.verifyUndefProperties(paramRefs, {
+ ctx.verifyReferences(propertyReferences, {
props: true
})
},
@@ -759,19 +699,16 @@ module.exports = {
const ctx = getVueComponentContext(vueData.node)
// Check for Vue 3.x render
- const paramsRefs = getParamsReferenceProperties(node)
- const ctxRefs = paramsRefs.getParam(0)
- ctx.verifyUndefProperties(ctxRefs)
+ const propertyReferences =
+ propertyReferenceExtractor.extractFromFunctionParam(node, 0)
+ ctx.verifyReferences(propertyReferences)
if (vueData.functional) {
// Check for Vue 2.x render & functional
- const propsRefs = new ReferencePropertiesImpl()
- for (const ref of iterateResolvedRefs(paramsRefs.getParam(1))) {
- if (ref.name === 'props') {
- propsRefs.merge(ref.tracker(context))
- }
- }
- ctx.verifyUndefProperties(propsRefs, {
+ const propertyReferencesForV2 =
+ propertyReferenceExtractor.extractFromFunctionParam(node, 1)
+
+ ctx.verifyReferences(propertyReferencesForV2.getNest('props'), {
props: true
})
}
@@ -785,10 +722,27 @@ module.exports = {
return
}
const ctx = getVueComponentContext(vueData.node)
- const usedProps = extractPatternOrThisReferences(node, context, false)
- ctx.verifyUndefProperties(usedProps)
+ const propertyReferences =
+ propertyReferenceExtractor.extractFromExpression(node, false)
+ ctx.verifyReferences(propertyReferences)
}
- })
+ }),
+ {
+ 'Program:exit'() {
+ const ctx = getVueComponentContextForTemplate()
+ if (!ctx) {
+ return
+ }
+ const styleVars = getStyleVariablesContext(context)
+ if (styleVars) {
+ ctx.verifyReferences(
+ propertyReferenceExtractor.extractFromStyleVariablesContext(
+ styleVars
+ )
+ )
+ }
+ }
+ }
)
const templateVisitor = {
@@ -796,40 +750,15 @@ module.exports = {
* @param {VExpressionContainer} node
*/
VExpressionContainer(node) {
- const globalScope =
- context.getSourceCode().scopeManager.globalScope ||
- context.getSourceCode().scopeManager.scopes[0]
-
- const refs = new ReferencePropertiesImpl()
- for (const id of getReferences(node.references)) {
- if (globalScope.set.has(id.name)) {
- continue
- }
- refs.addReference(id.name, id, (context) =>
- extractPatternOrThisReferences(id, context, true)
- )
- }
-
- const exported = [...vueComponentContextMap.keys()].find(isExportObject)
- const ctx = exported && vueComponentContextMap.get(exported)
-
- if (ctx) {
- ctx.verifyUndefProperties(refs)
- }
-
- /**
- * @param {ASTNode} node
- */
- function isExportObject(node) {
- let parent = node.parent
- while (parent) {
- if (parent.type === 'ExportDefaultDeclaration') {
- return true
- }
- parent = parent.parent
- }
- return false
+ const ctx = getVueComponentContextForTemplate()
+ if (!ctx) {
+ return
}
+ ctx.verifyReferences(
+ propertyReferenceExtractor.extractFromVExpressionContainer(node, {
+ ignoreGlobals: true
+ })
+ )
}
}
diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js
index 722e95c31..00da49d63 100644
--- a/lib/rules/no-unused-properties.js
+++ b/lib/rules/no-unused-properties.js
@@ -115,14 +115,6 @@ function getScope(context, currentNode) {
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
@@ -503,7 +495,10 @@ module.exports = {
const watcher = watcherOrExpose
// Process `watch: { foo /* <- this */ () {} }`
container.propertyReferences.push(
- propertyReferenceExtractor.extractFromPath(watcher.name)
+ propertyReferenceExtractor.extractFromPath(
+ watcher.name,
+ watcher.node
+ )
)
// Process `watch: { x: 'foo' /* <- this */ }`
@@ -513,24 +508,21 @@ module.exports = {
for (const handlerValueNode of utils.iterateWatchHandlerValues(
property
)) {
- if (
- handlerValueNode.type === 'Literal' ||
- handlerValueNode.type === 'TemplateLiteral'
- ) {
- const name = utils.getStringLiteralValue(handlerValueNode)
- if (name != null) {
- container.propertyReferences.push(
- propertyReferenceExtractor.extractFromName(name)
- )
- }
- }
+ container.propertyReferences.push(
+ propertyReferenceExtractor.extractFromNameLiteral(
+ handlerValueNode
+ )
+ )
}
}
}
} else if (watcherOrExpose.groupName === GROUP_EXPOSE) {
const expose = watcherOrExpose
container.propertyReferences.push(
- propertyReferenceExtractor.extractFromName(expose.name)
+ propertyReferenceExtractor.extractFromName(
+ expose.name,
+ expose.node
+ )
)
}
}
@@ -630,13 +622,11 @@ module.exports = {
'Program:exit'(node) {
const styleVars = getStyleVariablesContext(context)
if (styleVars) {
- for (const { id } of styleVars.references) {
- templatePropertiesContainer.propertyReferences.push(
- propertyReferenceExtractor.extractFromName(id.name, () =>
- propertyReferenceExtractor.extractFromExpression(id, true)
- )
+ templatePropertiesContainer.propertyReferences.push(
+ propertyReferenceExtractor.extractFromStyleVariablesContext(
+ styleVars
)
- }
+ )
}
if (!node.templateBody) {
reportUnusedProperties()
@@ -650,13 +640,9 @@ module.exports = {
* @param {VExpressionContainer} node
*/
VExpressionContainer(node) {
- for (const id of getReferences(node.references)) {
- templatePropertiesContainer.propertyReferences.push(
- propertyReferenceExtractor.extractFromName(id.name, () =>
- propertyReferenceExtractor.extractFromExpression(id, true)
- )
- )
- }
+ templatePropertiesContainer.propertyReferences.push(
+ propertyReferenceExtractor.extractFromVExpressionContainer(node)
+ )
},
/**
* @param {VAttribute} node
diff --git a/lib/utils/property-references.js b/lib/utils/property-references.js
index 2291ea717..f375170c3 100644
--- a/lib/utils/property-references.js
+++ b/lib/utils/property-references.js
@@ -9,7 +9,7 @@ const utils = require('./index')
const eslintUtils = require('eslint-utils')
/**
- * @typedef {'props' | 'data' | 'computed' | 'methods' | 'setup'} Group
+ * @typedef {import('./style-variables').StyleVariablesContext} StyleVariablesContext
*/
/**
* @typedef {object} IHasPropertyOption
@@ -18,6 +18,7 @@ const eslintUtils = require('eslint-utils')
/**
* @typedef {object} IPropertyReferences
* @property { (name: string, option?: IHasPropertyOption) => boolean } hasProperty
+ * @property { () => Map } allProperties
* @property { (name: string) => IPropertyReferences } getNest
*/
@@ -28,12 +29,14 @@ const eslintUtils = require('eslint-utils')
/** @type {IPropertyReferences} */
const ANY = {
hasProperty: () => true,
+ allProperties: () => new Map(),
getNest: () => ANY
}
/** @type {IPropertyReferences} */
const NEVER = {
hasProperty: () => false,
+ allProperties: () => new Map(),
getNest: () => NEVER
}
@@ -186,6 +189,10 @@ function definePropertyReferenceExtractor(context) {
return name === this.name
}
+ allProperties() {
+ return new Map([[this.name, { nodes: [this.node.property] }]])
+ }
+
/**
* @param {string} name
* @returns {IPropertyReferences}
@@ -213,6 +220,14 @@ function definePropertyReferenceExtractor(context) {
return Boolean(this.properties[name])
}
+ allProperties() {
+ const result = new Map()
+ for (const [name, nodes] of Object.entries(this.properties)) {
+ result.set(name, { nodes: nodes.map((node) => node.key) })
+ }
+ return result
+ }
+
/**
* @param {string} name
* @returns {IPropertyReferences}
@@ -429,6 +444,7 @@ function definePropertyReferenceExtractor(context) {
hasProperty(_name, options) {
return Boolean(options && options.unknownCallAsAny)
},
+ allProperties: () => new Map(),
getNest: () => ANY
}
}
@@ -446,6 +462,7 @@ function definePropertyReferenceExtractor(context) {
hasProperty(_name, options) {
return Boolean(options && options.unknownCallAsAny)
},
+ allProperties: () => new Map(),
getNest: () => ANY
}
}
@@ -482,9 +499,10 @@ function definePropertyReferenceExtractor(context) {
/**
* Extract the property references from path.
* @param {string} pathString
+ * @param {Identifier | Literal | TemplateLiteral} node
* @returns {IPropertyReferences}
*/
- function extractFromPath(pathString) {
+ function extractFromPath(pathString, node) {
return extractFromSegments(pathString.split('.'))
/**
@@ -498,21 +516,45 @@ function definePropertyReferenceExtractor(context) {
const segmentName = segments[0]
return {
hasProperty: (name) => name === segmentName,
+ allProperties: () => new Map([[segmentName, { nodes: [node] }]]),
getNest: (name) =>
name === segmentName ? extractFromSegments(segments.slice(1)) : NEVER
}
}
}
+ /**
+ * Extract the property references from name literal.
+ * @param {Expression} node
+ * @returns {IPropertyReferences}
+ */
+ function extractFromNameLiteral(node) {
+ const referenceName =
+ node.type === 'Literal' || node.type === 'TemplateLiteral'
+ ? utils.getStringLiteralValue(node)
+ : null
+ if (referenceName) {
+ return {
+ hasProperty: (name) => name === referenceName,
+ allProperties: () => new Map([[referenceName, { nodes: [node] }]]),
+ getNest: (name) => (name === referenceName ? ANY : NEVER)
+ }
+ } else {
+ return NEVER
+ }
+ }
+
/**
* Extract the property references from name.
* @param {string} referenceName
+ * @param {Expression|SpreadElement} nameNode
* @param { () => IPropertyReferences } [getNest]
* @returns {IPropertyReferences}
*/
- function extractFromName(referenceName, getNest) {
+ function extractFromName(referenceName, nameNode, getNest) {
return {
hasProperty: (name) => name === referenceName,
+ allProperties: () => new Map([[referenceName, { nodes: [nameNode] }]]),
getNest: (name) =>
name === referenceName ? (getNest ? getNest() : ANY) : NEVER
}
@@ -534,7 +576,7 @@ function definePropertyReferenceExtractor(context) {
// unknown name
return ANY
}
- return extractFromName(refName, () => {
+ return extractFromName(refName, nameNode, () => {
return extractFromExpression(node, false).getNest('value')
})
}
@@ -548,15 +590,67 @@ function definePropertyReferenceExtractor(context) {
const base = extractFromExpression(node, false)
return {
hasProperty: (name, option) => base.hasProperty(name, option),
+ allProperties: () => base.allProperties(),
getNest: (name) => base.getNest(name).getNest('value')
}
}
+
+ /**
+ * Extract the property references from VExpressionContainer.
+ * @param {VExpressionContainer} node
+ * @param {object} [options]
+ * @param {boolean} [options.ignoreGlobals]
+ * @returns {IPropertyReferences}
+ */
+ function extractFromVExpressionContainer(node, options) {
+ const ignoreGlobals = options && options.ignoreGlobals
+
+ /** @type { (name:string)=>boolean } */
+ let ignoreRef = () => false
+ if (ignoreGlobals) {
+ const globalScope =
+ context.getSourceCode().scopeManager.globalScope ||
+ context.getSourceCode().scopeManager.scopes[0]
+
+ ignoreRef = (name) => globalScope.set.has(name)
+ }
+ const references = []
+ for (const id of node.references
+ .filter((ref) => ref.variable == null)
+ .map((ref) => ref.id)) {
+ if (ignoreRef(id.name)) {
+ continue
+ }
+ references.push(
+ extractFromName(id.name, id, () => extractFromExpression(id, true))
+ )
+ }
+ return mergePropertyReferences(references)
+ }
+ /**
+ * Extract the property references from StyleVariablesContext.
+ * @param {StyleVariablesContext} ctx
+ * @returns {IPropertyReferences}
+ */
+ function extractFromStyleVariablesContext(ctx) {
+ const references = []
+ for (const { id } of ctx.references) {
+ references.push(
+ extractFromName(id.name, id, () => extractFromExpression(id, true))
+ )
+ }
+ return mergePropertyReferences(references)
+ }
+
return {
extractFromExpression,
extractFromPattern,
extractFromFunctionParam,
extractFromPath,
- extractFromName
+ extractFromName,
+ extractFromNameLiteral,
+ extractFromVExpressionContainer,
+ extractFromStyleVariablesContext
}
}
@@ -594,6 +688,21 @@ class PropertyReferencesForMerge {
return this.references.some((ref) => ref.hasProperty(name, option))
}
+ allProperties() {
+ const result = new Map()
+ for (const reference of this.references) {
+ for (const [name, { nodes }] of reference.allProperties()) {
+ const r = result.get(name)
+ if (r) {
+ r.nodes = [...new Set([...r.nodes, ...nodes])]
+ } else {
+ result.set(name, { nodes: [...nodes] })
+ }
+ }
+ }
+ return result
+ }
+
/**
* @param {string} name
* @returns {IPropertyReferences}
diff --git a/lib/utils/style-variables/index.js b/lib/utils/style-variables/index.js
index 63caf208a..3127166a4 100644
--- a/lib/utils/style-variables/index.js
+++ b/lib/utils/style-variables/index.js
@@ -1,9 +1,5 @@
const { isVElement } = require('..')
-module.exports = {
- getStyleVariablesContext
-}
-
class StyleVariablesContext {
/**
* @param {RuleContext} context
@@ -31,6 +27,11 @@ class StyleVariablesContext {
}
}
+module.exports = {
+ getStyleVariablesContext,
+ StyleVariablesContext
+}
+
/** @type {Map} */
const cache = new Map()
/**
diff --git a/tests/lib/rules/no-undef-properties.js b/tests/lib/rules/no-undef-properties.js
index 1d8bb5894..09e16bda9 100644
--- a/tests/lib/rules/no-undef-properties.js
+++ b/tests/lib/rules/no-undef-properties.js
@@ -1034,6 +1034,41 @@ tester.run('no-undef-properties', rule, {
line: 13
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ ]{{ name }}: {{ count }}
+
+ {{ label }}: {{ cnt }}
+
+
+ `,
+ errors: [
+ {
+ message: "'label' is not defined.",
+ line: 6
+ },
+ {
+ message: "'cnt' is not defined.",
+ line: 6
+ },
+ {
+ message: "'undef' is not defined.",
+ line: 16
+ }
+ ]
}
]
})