diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md index 8acad1f54..1567d6e60 100644 --- a/docs/user-guide/index.md +++ b/docs/user-guide/index.md @@ -1,3 +1,7 @@ +--- +outline: deep +--- + # User Guide ## :cd: Installation @@ -386,3 +390,41 @@ Try searching for existing issues. If it does not exist, you should open a new issue and share your repository to reproduce the issue. [vue-eslint-parser]: https://github.com/vuejs/vue-eslint-parser + +### Auto Imports Support + +In [Nuxt 3](https://nuxt.com/) or with [`unplugin-auto-import`](https://github.com/unplugin/unplugin-auto-import), Vue APIs can be auto imported. To make rules like [`vue/no-ref-as-operand`](/rules/no-ref-as-operand.html) or [`vue/no-watch-after-await`](/rules/no-watch-after-await.html) work correctly with them, you can specify them in ESLint's [`globals`](https://eslint.org/docs/latest/use/configure/configuration-files-new#configuring-global-variables) options: + +::: code-group + +```json [Legacy Config] +// .eslintrc +{ + "globals": { + "ref": "readonly", + "computed": "readonly", + "watch": "readonly", + "watchEffect": "readonly", + // ...more APIs + } +} +``` + +```js [Flat Config] +// eslint.config.js +export default [ + { + languageOptions: { + globals: { + ref: 'readonly', + computed: 'readonly', + watch: 'readonly', + watchEffect: 'readonly', + // ...more APIs + } + } + } +] +``` + +::: diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js index 6ee4373f1..afe9cd350 100644 --- a/lib/rules/no-async-in-computed-properties.js +++ b/lib/rules/no-async-in-computed-properties.js @@ -263,14 +263,11 @@ module.exports = { /** @param {Program} program */ Program(program) { const tracker = new ReferenceTracker(utils.getScope(context, program)) - const traceMap = utils.createCompositionApiTraceMap({ - [ReferenceTracker.ESM]: true, + for (const { node } of utils.iterateReferencesTraceMap(tracker, { computed: { [ReferenceTracker.CALL]: true } - }) - - for (const { node } of tracker.iterateEsmReferences(traceMap)) { + })) { if (node.type !== 'CallExpression') { continue } diff --git a/lib/rules/no-lifecycle-after-await.js b/lib/rules/no-lifecycle-after-await.js index 21416f893..c695ae420 100644 --- a/lib/rules/no-lifecycle-after-await.js +++ b/lib/rules/no-lifecycle-after-await.js @@ -63,19 +63,18 @@ module.exports = { /** @param {Program} program */ Program(program) { const tracker = new ReferenceTracker(utils.getScope(context, program)) - const traceMap = { - /** @type {TraceMap} */ - vue: { - [ReferenceTracker.ESM]: true - } - } + /** @type {TraceMap} */ + const traceMap = {} for (const lifecycleHook of LIFECYCLE_HOOKS) { - traceMap.vue[lifecycleHook] = { + traceMap[lifecycleHook] = { [ReferenceTracker.CALL]: true } } - for (const { node } of tracker.iterateEsmReferences(traceMap)) { + for (const { node } of utils.iterateReferencesTraceMap( + tracker, + traceMap + )) { lifecycleHookCallNodes.add(node) } } diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js index 76127c37f..b0fbfc3d9 100644 --- a/lib/rules/no-side-effects-in-computed-properties.js +++ b/lib/rules/no-side-effects-in-computed-properties.js @@ -183,14 +183,12 @@ module.exports = { /** @param {Program} program */ Program(program) { const tracker = new ReferenceTracker(utils.getScope(context, program)) - const traceMap = utils.createCompositionApiTraceMap({ - [ReferenceTracker.ESM]: true, + + for (const { node } of utils.iterateReferencesTraceMap(tracker, { computed: { [ReferenceTracker.CALL]: true } - }) - - for (const { node } of tracker.iterateEsmReferences(traceMap)) { + })) { if (node.type !== 'CallExpression') { continue } diff --git a/lib/rules/no-watch-after-await.js b/lib/rules/no-watch-after-await.js index cbe1772bd..7c4e852f2 100644 --- a/lib/rules/no-watch-after-await.js +++ b/lib/rules/no-watch-after-await.js @@ -78,19 +78,15 @@ module.exports = { /** @param {Program} program */ Program(program) { const tracker = new ReferenceTracker(utils.getScope(context, program)) - const traceMap = { - vue: { - [ReferenceTracker.ESM]: true, - watch: { - [ReferenceTracker.CALL]: true - }, - watchEffect: { - [ReferenceTracker.CALL]: true - } - } - } - for (const { node } of tracker.iterateEsmReferences(traceMap)) { + for (const { node } of utils.iterateReferencesTraceMap(tracker, { + watch: { + [ReferenceTracker.CALL]: true + }, + watchEffect: { + [ReferenceTracker.CALL]: true + } + })) { watchCallNodes.add(node) } } diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js index 54d2553a1..74fff9d1f 100644 --- a/lib/rules/return-in-computed-property.js +++ b/lib/rules/return-in-computed-property.js @@ -57,14 +57,16 @@ module.exports = { /** @param {Program} program */ Program(program) { const tracker = new ReferenceTracker(utils.getScope(context, program)) - const traceMap = utils.createCompositionApiTraceMap({ - [ReferenceTracker.ESM]: true, + const map = { computed: { [ReferenceTracker.CALL]: true } - }) + } - for (const { node } of tracker.iterateEsmReferences(traceMap)) { + for (const { node } of utils.iterateReferencesTraceMap( + tracker, + map + )) { if (node.type !== 'CallExpression') { continue } diff --git a/lib/utils/index.js b/lib/utils/index.js index 6773611cb..f16bcef2c 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -62,7 +62,10 @@ const VUE_BUILTIN_ELEMENT_NAMES = new Set(require('./vue-builtin-elements')) const path = require('path') const vueEslintParser = require('vue-eslint-parser') const { traverseNodes, getFallbackKeys, NS } = vueEslintParser.AST -const { findVariable } = require('@eslint-community/eslint-utils') +const { + findVariable, + ReferenceTracker +} = require('@eslint-community/eslint-utils') const { getComponentPropsFromTypeDefine, getComponentEmitsFromTypeDefine, @@ -2104,14 +2107,38 @@ module.exports = { iterateWatchHandlerValues, /** - * Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports + * Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports, or '#imports' from unimport * @param {import('@eslint-community/eslint-utils').TYPES.TraceMap} map */ createCompositionApiTraceMap: (map) => ({ vue: map, - '@vue/composition-api': map + '@vue/composition-api': map, + '#imports': map }), + /** + * Iterates all references in the given trace map. + * Take the third argument option to detect auto-imported references. + * + * @param {import('@eslint-community/eslint-utils').ReferenceTracker} tracker + * @param {import('@eslint-community/eslint-utils').TYPES.TraceMap} map + * @returns {ReturnType} + */ + *iterateReferencesTraceMap(tracker, map) { + const esmTraceMap = this.createCompositionApiTraceMap({ + ...map, + [ReferenceTracker.ESM]: true + }) + + for (const ref of tracker.iterateEsmReferences(esmTraceMap)) { + yield ref + } + + for (const ref of tracker.iterateGlobalReferences(map)) { + yield ref + } + }, + /** * Checks whether or not the tokens of two given nodes are same. * @param {ASTNode} left A node 1 to compare. diff --git a/lib/utils/property-references.js b/lib/utils/property-references.js index 1ed26640f..57fe59975 100644 --- a/lib/utils/property-references.js +++ b/lib/utils/property-references.js @@ -119,25 +119,21 @@ function definePropertyReferenceExtractor( context.getSourceCode().scopeManager.scopes[0] ) const toRefNodes = new Set() - for (const { node } of tracker.iterateEsmReferences( - utils.createCompositionApiTraceMap({ - [eslintUtils.ReferenceTracker.ESM]: true, - toRef: { - [eslintUtils.ReferenceTracker.CALL]: true - } - }) - )) { + for (const { node } of utils.iterateReferencesTraceMap(tracker, { + [eslintUtils.ReferenceTracker.ESM]: true, + toRef: { + [eslintUtils.ReferenceTracker.CALL]: true + } + })) { toRefNodes.add(node) } const toRefsNodes = new Set() - for (const { node } of tracker.iterateEsmReferences( - utils.createCompositionApiTraceMap({ - [eslintUtils.ReferenceTracker.ESM]: true, - toRefs: { - [eslintUtils.ReferenceTracker.CALL]: true - } - }) - )) { + for (const { node } of utils.iterateReferencesTraceMap(tracker, { + [eslintUtils.ReferenceTracker.ESM]: true, + toRefs: { + [eslintUtils.ReferenceTracker.CALL]: true + } + })) { toRefsNodes.add(node) } diff --git a/lib/utils/ref-object-references.js b/lib/utils/ref-object-references.js index 538fbb08f..fe6f26117 100644 --- a/lib/utils/ref-object-references.js +++ b/lib/utils/ref-object-references.js @@ -82,8 +82,7 @@ const cacheForReactiveVariableReferences = new WeakMap() */ function* iterateDefineRefs(globalScope) { const tracker = new ReferenceTracker(globalScope) - const traceMap = utils.createCompositionApiTraceMap({ - [ReferenceTracker.ESM]: true, + for (const { node, path } of utils.iterateReferencesTraceMap(tracker, { ref: { [ReferenceTracker.CALL]: true }, @@ -102,8 +101,7 @@ function* iterateDefineRefs(globalScope) { toRefs: { [ReferenceTracker.CALL]: true } - }) - for (const { node, path } of tracker.iterateEsmReferences(traceMap)) { + })) { const expr = /** @type {CallExpression} */ (node) yield { node: expr, diff --git a/tests/lib/rules/no-ref-as-operand.js b/tests/lib/rules/no-ref-as-operand.js index f08a9e1c2..62e29275c 100644 --- a/tests/lib/rules/no-ref-as-operand.js +++ b/tests/lib/rules/no-ref-as-operand.js @@ -822,6 +822,54 @@ tester.run('no-ref-as-operand', rule, { line: 9 } ] + }, + // Auto-import + { + code: ` + let count = ref(0) + + count++ // error + console.log(count + 1) // error + console.log(1 + count) // error + `, + output: ` + let count = ref(0) + + count.value++ // error + console.log(count.value + 1) // error + console.log(1 + count.value) // error + `, + errors: [ + { + message: + 'Must use `.value` to read or write the value wrapped by `ref()`.', + line: 4, + column: 7, + endLine: 4, + endColumn: 12 + }, + { + message: + 'Must use `.value` to read or write the value wrapped by `ref()`.', + line: 5, + column: 19, + endLine: 5, + endColumn: 24 + }, + { + message: + 'Must use `.value` to read or write the value wrapped by `ref()`.', + line: 6, + column: 23, + endLine: 6, + endColumn: 28 + } + ], + languageOptions: { + globals: { + ref: 'readonly' + } + } } ] })