vue/no-undef-properties in v9.30 still emits error for Vuex mutation or getter
Checklist
- [x] I have tried restarting my IDE and the issue persists.
- [x] I have read the FAQ and my problem is not listed.
Tell us about your environment
- ESLint version: 8.57.1
- eslint-plugin-vue version: 9.30.0
- Vue version: 2.6.12
- Node version: 18.19.1
- Operating System: Windows 7 Professional x64 SP1
Please show your full configuration:
// https://eslint.org/docs/user-guide/configuring
const path = require('path');
module.exports = {
root: true,
parserOptions: {
parser: '@babel/eslint-parser',
ecmaVersion: 2020,
requireConfigFile: false,
babelOptions:
{
configFile: path.resolve(__dirname, './babel.config.js')
}
},
env:
{
browser: true,
node: true,
},
extends: [
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/recommended',
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'@vue/eslint-config-standard',
],
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
semi: ['error', 'always'],
'brace-style': ['error', 'allman'],
'space-before-function-paren': ['error', {
anonymous: 'never',
named: 'never',
asyncArrow: 'always'
}],
'object-curly-newline': [
'error',
{
ObjectExpression: {
multiline: true,
minProperties: 2,
consistent: true
},
ObjectPattern: { multiline: true },
ImportDeclaration: { multiline: true },
ExportDeclaration: {
multiline: true,
minProperties: 3
}
}
],
'object-property-newline': [
'error',
{ allowAllPropertiesOnSameLine: false }
],
'comma-dangle': [
'error',
{
arrays: 'only-multiline',
objects: 'only-multiline',
imports: 'never',
exports: 'never',
functions: 'never'
}
],
quotes: [
'error',
'single',
{
avoidEscape: true,
allowTemplateLiterals: true
}
],
'one-var': [
'error',
{
uninitialized: 'always',
initialized: 'never'
}
],
'block-scoped-var': "error",
'default-param-last': ["error"],
'func-names': ["error", "as-needed"],
'func-style': ["error", "declaration", { "allowArrowFunctions": true }],
'max-statements-per-line': ["error", { "max": 1 }],
'no-confusing-arrow': "error",
'no-constant-binary-expression': "error",
'no-dupe-else-if': "error",
'no-lonely-if': "error",
'no-loop-func': "error",
'nonblock-statement-body-position': ["error", "beside"],
'no-negated-in-lhs': "error",
'no-shadow': "error",
'no-spaced-func': "error",
'no-unsafe-optional-chaining': ["error", { "disallowArithmeticOperators": true }],
'no-useless-concat': "error",
'semi-style': ["error", "last"],
'vars-on-top': "error",
'vue/no-undef-properties': 'error',
}
}
What did you do?
import Vue from 'vue';
import App from './App.vue';
import store from './store/store';
import { mapGetters, mapMutations } from 'vuex';
new Vue({
name: 'RootVue',
computed:
{
...mapGetters(['getCountryMap']),
},
watch:
{
getCountryMap()
{
this.updatePrefix();
},
},
created()
{
this.setCountries([]);
},
methods:
{
...mapMutations(['setCountries']),
updatePrefix()
{
//
},
},
store,
render: h => h(App)
}).$mount('#app');
What did you expect to happen?
I expect no linting error of type vue/no-undef-properties to be reported for lines 14:5 and 21:10
What actually happened?
You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
ERROR in [eslint]
Z:\_\src\main.js
14:5 error 'getCountryMap' is not defined vue/no-undef-properties
21:10 error 'setCountries' is not defined vue/no-undef-properties
✖ 2 problems (2 errors, 0 warnings)
webpack compiled with 1 error
Repository to reproduce this issue
https://codesandbox.io/p/devbox/gallant-swirles-ljp5n5
Additional context from https://github.com/vuejs/eslint-plugin-vue/pull/2513#issuecomment-2459347922:
There is a problem with this fix - it works only if
mapGettersormapStateormapMutationsappears in the source code BEFORE the actual reference to the property or method. For example, in the following code no linting error will be emittedcreated() { if (this.getCountries.length === 0) this.fetchCountries(); }, methods: { ...mapMutations(['setCountries']), fetchCountries() { // ... fetching from server this.setCountries(data); } }However, in the following code a linting error
setCountries is not defined vue/no-undef-propertieswill be emittedcreated() { if (this.getCountries.length === 0) { // ... fetching from server this.setCountries(data); // <==== linting error - but it should not be } }, methods: { ...mapMutations(['setCountries']), }
Another problem is that the fix for Vuex/Pinia in vue/no-undef-properties rule only recognizes string literals - but ignores constants. So for example code like this will fail with a linting error:
import { mapGetters } from 'vuex';
const GET_SOMETHING = 'GET_SOMETHING';
export default
{
computed:
{
...mapGetters([GET_SOMETHING]),
},
methods:
{
someFunction()
{
if (this[GET_SOMETHING]) // <===== here will be a linting error "vue/no-undef-properties"
{
// .... some code
}
}
}
}
Having the same issue with ...mapGetters() ➕ (eslint 8.56.0, eslint-plugin-vue version version 9.32.0)
Here is a couple of patches which I am using with CustomPatch
Index: \eslint-plugin-vue\lib\rules\match-component-file-name.js
===================================================================
--- \eslint-plugin-vue\lib\rules\match-component-file-name.js
+++ \eslint-plugin-vue\lib\rules\match-component-file-name.js
@@ -6,8 +6,9 @@
const utils = require('../utils')
const casing = require('../utils/casing')
const path = require('path')
+const fs = require('fs')
/**
* @param {Expression | SpreadElement} node
* @returns {node is (Literal | TemplateLiteral)}
@@ -64,9 +65,9 @@
? extensionsArray
: ['jsx']
const extension = path.extname(context.getFilename())
- const filename = path.basename(context.getFilename(), extension)
+ const filename = path.basename(fs.realpathSync.native(context.getFilename()), extension) // TMCDOS - check the filesystem, not how it is imported by Webpack (on case-insensitive FS)
/** @type {Rule.ReportDescriptor[]} */
const errors = []
let componentCount = 0
Index: \eslint-plugin-vue\lib\rules\no-bare-strings-in-template.js
===================================================================
--- \eslint-plugin-vue\lib\rules\no-bare-strings-in-template.js
+++ \eslint-plugin-vue\lib\rules\no-bare-strings-in-template.js
@@ -131,9 +131,14 @@
directives: {
type: 'array',
items: { type: 'string', pattern: '^v-' },
uniqueItems: true
- }
+ },
+ components: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true
+ }
},
additionalProperties: false
}
],
@@ -151,8 +156,10 @@
/** @type {string[]} */
const allowlist = opts.allowlist || DEFAULT_ALLOWLIST
const attributes = parseTargetAttrs(opts.attributes || DEFAULT_ATTRIBUTES)
const directives = opts.directives || DEFAULT_DIRECTIVES
+ /** @type {RegExp[]} */
+ const ignoreComponents = (opts.components || []).map(regexp.toRegExp)
const allowlistRe = new RegExp(
allowlist.map((w) => regexp.escape(w)).join('|'),
'gu'
@@ -194,11 +201,25 @@
return (attributes.cache[tagName] = new Set(result))
}
+ /**
+ * Checks whether the given node should be ignored.
+ * @param {VText} node text node
+ * @returns {boolean} `true` if the given node should be ignored.
+ */
+ function isIgnored({ parent }) {
+ return (
+ parent.type === 'VElement' &&
+ ignoreComponents.some((re) => re.test(parent.rawName))
+ )
+ }
+
return utils.defineTemplateBodyVisitor(context, {
/** @param {VText} node */
VText(node) {
+ if (isIgnored(node)) return
+
if (getBareString(node.value)) {
context.report({
node,
messageId: 'unexpected'
Index: \eslint-plugin-vue\lib\rules\no-undef-properties.js
===================================================================
--- \eslint-plugin-vue\lib\rules\no-undef-properties.js
+++ \eslint-plugin-vue\lib\rules\no-undef-properties.js
@@ -371,8 +371,13 @@
}
} else if (arg.type === 'ArrayExpression') {
// e.g. `mapMutations(['add'])`
for (const element of arg.elements) {
+ if (element && element.type === 'Identifier' && typeof element.name === 'string') // TMCDOS - support constants for Vuex names
+ {
+ propertiesDefinedByStoreHelpers.add(element.name);
+ continue
+ }
if (!element || !utils.isStringLiteral(element)) {
continue
}
const name = utils.getStringLiteralValue(element)