eslint-plugin-vue icon indicating copy to clipboard operation
eslint-plugin-vue copied to clipboard

vue/no-undef-properties in v9.30 still emits error for Vuex mutation or getter

Open tmcdos opened this issue 1 year ago • 4 comments

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

shot-1

Repository to reproduce this issue

https://codesandbox.io/p/devbox/gallant-swirles-ljp5n5

shot-2

tmcdos avatar Nov 06 '24 13:11 tmcdos

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 mapGetters or mapState or mapMutations appears in the source code BEFORE the actual reference to the property or method. For example, in the following code no linting error will be emitted

  created()
  {
    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-properties will be emitted

  created()
  {
    if (this.getCountries.length === 0)
    {
          // ... fetching from server
          this.setCountries(data); // <==== linting error - but it should not be
    }
  },
methods:
{
        ...mapMutations(['setCountries']),
}

FloEdelmann avatar Nov 06 '24 14:11 FloEdelmann

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
        }
    }
  }
}

tmcdos avatar Nov 06 '24 15:11 tmcdos

Having the same issue with ...mapGetters() ➕ (eslint 8.56.0, eslint-plugin-vue version version 9.32.0)

markusschmitz53 avatar Jan 22 '25 09:01 markusschmitz53

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)

tmcdos avatar Jan 22 '25 10:01 tmcdos