Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3abc04c

Browse files
antfuFloEdelmann
andauthored
feat: support Vue APIs from auto imports (#2422)
Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
1 parent 7c71c48 commit 3abc04c

10 files changed

+160
-57
lines changed

‎docs/user-guide/index.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
---
2+
outline: deep
3+
---
4+
15
# User Guide
26

37
## :cd: Installation
@@ -386,3 +390,41 @@ Try searching for existing issues.
386390
If it does not exist, you should open a new issue and share your repository to reproduce the issue.
387391

388392
[vue-eslint-parser]: https://github.com/vuejs/vue-eslint-parser
393+
394+
### Auto Imports Support
395+
396+
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:
397+
398+
::: code-group
399+
400+
```json [Legacy Config]
401+
// .eslintrc
402+
{
403+
"globals": {
404+
"ref": "readonly",
405+
"computed": "readonly",
406+
"watch": "readonly",
407+
"watchEffect": "readonly",
408+
// ...more APIs
409+
}
410+
}
411+
```
412+
413+
```js [Flat Config]
414+
// eslint.config.js
415+
export default [
416+
{
417+
languageOptions: {
418+
globals: {
419+
ref: 'readonly',
420+
computed: 'readonly',
421+
watch: 'readonly',
422+
watchEffect: 'readonly',
423+
// ...more APIs
424+
}
425+
}
426+
}
427+
]
428+
```
429+
430+
:::

‎lib/rules/no-async-in-computed-properties.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,11 @@ module.exports = {
263263
/** @param {Program} program */
264264
Program(program) {
265265
const tracker = new ReferenceTracker(utils.getScope(context, program))
266-
const traceMap = utils.createCompositionApiTraceMap({
267-
[ReferenceTracker.ESM]: true,
266+
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
268267
computed: {
269268
[ReferenceTracker.CALL]: true
270269
}
271-
})
272-
273-
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
270+
})) {
274271
if (node.type !== 'CallExpression') {
275272
continue
276273
}

‎lib/rules/no-lifecycle-after-await.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,18 @@ module.exports = {
6363
/** @param {Program} program */
6464
Program(program) {
6565
const tracker = new ReferenceTracker(utils.getScope(context, program))
66-
const traceMap = {
67-
/** @type {TraceMap} */
68-
vue: {
69-
[ReferenceTracker.ESM]: true
70-
}
71-
}
66+
/** @type {TraceMap} */
67+
const traceMap = {}
7268
for (const lifecycleHook of LIFECYCLE_HOOKS) {
73-
traceMap.vue[lifecycleHook] = {
69+
traceMap[lifecycleHook] = {
7470
[ReferenceTracker.CALL]: true
7571
}
7672
}
7773

78-
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
74+
for (const { node } of utils.iterateReferencesTraceMap(
75+
tracker,
76+
traceMap
77+
)) {
7978
lifecycleHookCallNodes.add(node)
8079
}
8180
}

‎lib/rules/no-side-effects-in-computed-properties.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,12 @@ module.exports = {
183183
/** @param {Program} program */
184184
Program(program) {
185185
const tracker = new ReferenceTracker(utils.getScope(context, program))
186-
consttraceMap=utils.createCompositionApiTraceMap({
187-
[ReferenceTracker.ESM]: true,
186+
187+
for(const{ node }ofutils.iterateReferencesTraceMap(tracker,{
188188
computed: {
189189
[ReferenceTracker.CALL]: true
190190
}
191-
})
192-
193-
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
191+
})) {
194192
if (node.type !== 'CallExpression') {
195193
continue
196194
}

‎lib/rules/no-watch-after-await.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,15 @@ module.exports = {
7878
/** @param {Program} program */
7979
Program(program) {
8080
const tracker = new ReferenceTracker(utils.getScope(context, program))
81-
const traceMap = {
82-
vue: {
83-
[ReferenceTracker.ESM]: true,
84-
watch: {
85-
[ReferenceTracker.CALL]: true
86-
},
87-
watchEffect: {
88-
[ReferenceTracker.CALL]: true
89-
}
90-
}
91-
}
9281

93-
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
82+
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
83+
watch: {
84+
[ReferenceTracker.CALL]: true
85+
},
86+
watchEffect: {
87+
[ReferenceTracker.CALL]: true
88+
}
89+
})) {
9490
watchCallNodes.add(node)
9591
}
9692
}

‎lib/rules/return-in-computed-property.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,16 @@ module.exports = {
5757
/** @param {Program} program */
5858
Program(program) {
5959
const tracker = new ReferenceTracker(utils.getScope(context, program))
60-
const traceMap = utils.createCompositionApiTraceMap({
61-
[ReferenceTracker.ESM]: true,
60+
const map = {
6261
computed: {
6362
[ReferenceTracker.CALL]: true
6463
}
65-
})
64+
}
6665

67-
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
66+
for (const { node } of utils.iterateReferencesTraceMap(
67+
tracker,
68+
map
69+
)) {
6870
if (node.type !== 'CallExpression') {
6971
continue
7072
}

‎lib/utils/index.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ const VUE_BUILTIN_ELEMENT_NAMES = new Set(require('./vue-builtin-elements'))
6262
const path = require('path')
6363
const vueEslintParser = require('vue-eslint-parser')
6464
const { traverseNodes, getFallbackKeys, NS } = vueEslintParser.AST
65-
const { findVariable } = require('@eslint-community/eslint-utils')
65+
const {
66+
findVariable,
67+
ReferenceTracker
68+
} = require('@eslint-community/eslint-utils')
6669
const {
6770
getComponentPropsFromTypeDefine,
6871
getComponentEmitsFromTypeDefine,
@@ -2104,14 +2107,38 @@ module.exports = {
21042107
iterateWatchHandlerValues,
21052108

21062109
/**
2107-
* Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports
2110+
* Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports, or '#imports' from unimport
21082111
* @param {import('@eslint-community/eslint-utils').TYPES.TraceMap} map
21092112
*/
21102113
createCompositionApiTraceMap: (map) => ({
21112114
vue: map,
2112-
'@vue/composition-api': map
2115+
'@vue/composition-api': map,
2116+
'#imports': map
21132117
}),
21142118

2119+
/**
2120+
* Iterates all references in the given trace map.
2121+
* Take the third argument option to detect auto-imported references.
2122+
*
2123+
* @param {import('@eslint-community/eslint-utils').ReferenceTracker} tracker
2124+
* @param {import('@eslint-community/eslint-utils').TYPES.TraceMap} map
2125+
* @returns {ReturnType<import('@eslint-community/eslint-utils').ReferenceTracker['iterateEsmReferences']>}
2126+
*/
2127+
*iterateReferencesTraceMap(tracker, map) {
2128+
const esmTraceMap = this.createCompositionApiTraceMap({
2129+
...map,
2130+
[ReferenceTracker.ESM]: true
2131+
})
2132+
2133+
for (const ref of tracker.iterateEsmReferences(esmTraceMap)) {
2134+
yield ref
2135+
}
2136+
2137+
for (const ref of tracker.iterateGlobalReferences(map)) {
2138+
yield ref
2139+
}
2140+
},
2141+
21152142
/**
21162143
* Checks whether or not the tokens of two given nodes are same.
21172144
* @param {ASTNode} left A node 1 to compare.

‎lib/utils/property-references.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,25 +119,21 @@ function definePropertyReferenceExtractor(
119119
context.getSourceCode().scopeManager.scopes[0]
120120
)
121121
const toRefNodes = new Set()
122-
for (const { node } of tracker.iterateEsmReferences(
123-
utils.createCompositionApiTraceMap({
124-
[eslintUtils.ReferenceTracker.ESM]: true,
125-
toRef: {
126-
[eslintUtils.ReferenceTracker.CALL]: true
127-
}
128-
})
129-
)) {
122+
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
123+
[eslintUtils.ReferenceTracker.ESM]: true,
124+
toRef: {
125+
[eslintUtils.ReferenceTracker.CALL]: true
126+
}
127+
})) {
130128
toRefNodes.add(node)
131129
}
132130
const toRefsNodes = new Set()
133-
for (const { node } of tracker.iterateEsmReferences(
134-
utils.createCompositionApiTraceMap({
135-
[eslintUtils.ReferenceTracker.ESM]: true,
136-
toRefs: {
137-
[eslintUtils.ReferenceTracker.CALL]: true
138-
}
139-
})
140-
)) {
131+
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
132+
[eslintUtils.ReferenceTracker.ESM]: true,
133+
toRefs: {
134+
[eslintUtils.ReferenceTracker.CALL]: true
135+
}
136+
})) {
141137
toRefsNodes.add(node)
142138
}
143139

‎lib/utils/ref-object-references.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ const cacheForReactiveVariableReferences = new WeakMap()
8282
*/
8383
function* iterateDefineRefs(globalScope) {
8484
const tracker = new ReferenceTracker(globalScope)
85-
const traceMap = utils.createCompositionApiTraceMap({
86-
[ReferenceTracker.ESM]: true,
85+
for (const { node, path } of utils.iterateReferencesTraceMap(tracker, {
8786
ref: {
8887
[ReferenceTracker.CALL]: true
8988
},
@@ -102,8 +101,7 @@ function* iterateDefineRefs(globalScope) {
102101
toRefs: {
103102
[ReferenceTracker.CALL]: true
104103
}
105-
})
106-
for (const { node, path } of tracker.iterateEsmReferences(traceMap)) {
104+
})) {
107105
const expr = /** @type {CallExpression} */ (node)
108106
yield {
109107
node: expr,

‎tests/lib/rules/no-ref-as-operand.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,54 @@ tester.run('no-ref-as-operand', rule, {
822822
line: 9
823823
}
824824
]
825+
},
826+
// Auto-import
827+
{
828+
code: `
829+
let count = ref(0)
830+
831+
count++ // error
832+
console.log(count + 1) // error
833+
console.log(1 + count) // error
834+
`,
835+
output: `
836+
let count = ref(0)
837+
838+
count.value++ // error
839+
console.log(count.value + 1) // error
840+
console.log(1 + count.value) // error
841+
`,
842+
errors: [
843+
{
844+
message:
845+
'Must use `.value` to read or write the value wrapped by `ref()`.',
846+
line: 4,
847+
column: 7,
848+
endLine: 4,
849+
endColumn: 12
850+
},
851+
{
852+
message:
853+
'Must use `.value` to read or write the value wrapped by `ref()`.',
854+
line: 5,
855+
column: 19,
856+
endLine: 5,
857+
endColumn: 24
858+
},
859+
{
860+
message:
861+
'Must use `.value` to read or write the value wrapped by `ref()`.',
862+
line: 6,
863+
column: 23,
864+
endLine: 6,
865+
endColumn: 28
866+
}
867+
],
868+
languageOptions: {
869+
globals: {
870+
ref: 'readonly'
871+
}
872+
}
825873
}
826874
]
827875
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /