-
-
Notifications
You must be signed in to change notification settings - Fork 681
/
Copy pathprefer-import-from-vue.js
131 lines (124 loc) · 3.36 KB
/
prefer-import-from-vue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'
const vue3ExportNames = new Set(require('../utils/vue3-export-names.json'))
const TARGET_AT_VUE_MODULES = new Set([
'@vue/runtime-dom',
'@vue/runtime-core',
'@vue/reactivity',
'@vue/shared'
])
// Modules with the names of a subset of vue.
const SUBSET_AT_VUE_MODULES = new Set(['@vue/runtime-dom', '@vue/runtime-core'])
/**
* @param {ImportDeclaration} node
*/
function* extractImportNames(node) {
for (const specifier of node.specifiers) {
switch (specifier.type) {
case 'ImportDefaultSpecifier': {
yield 'default'
break
}
case 'ImportNamespaceSpecifier': {
yield null // all
break
}
case 'ImportSpecifier': {
yield specifier.imported.name
break
}
}
}
}
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: "enforce import from 'vue' instead of import from '@vue/*'",
categories: ['vue3-essential'],
url: 'https://eslint.vuejs.org/rules/prefer-import-from-vue.html'
},
fixable: 'code',
schema: [],
messages: {
importedAtVue: "Import from 'vue' instead of '{{source}}'."
}
},
/**
* @param {RuleContext} context
* @returns {RuleListener}
*/
create(context) {
/**
*
* @param {Literal & { value: string }} source
* @param { () => boolean } fixable
*/
function verifySource(source, fixable) {
if (!TARGET_AT_VUE_MODULES.has(source.value)) {
return
}
context.report({
node: source,
messageId: 'importedAtVue',
data: { source: source.value },
fix: fixable()
? (fixer) =>
fixer.replaceTextRange(
[source.range[0] + 1, source.range[1] - 1],
'vue'
)
: null
})
}
return {
ImportDeclaration(node) {
// Skip imports without specifiers in `.d.ts` files
if (
node.specifiers.length === 0 &&
context.getFilename().endsWith('.d.ts')
)
return
verifySource(node.source, () => {
if (SUBSET_AT_VUE_MODULES.has(node.source.value)) {
// If the module is a subset of 'vue', we can safely change it to 'vue'.
return true
}
for (const name of extractImportNames(node)) {
if (name == null) {
return false // import all
}
if (!vue3ExportNames.has(name)) {
// If there is a name that is not exported from 'vue', it will not be auto-fixed.
return false
}
}
return true
})
},
ExportNamedDeclaration(node) {
if (node.source) {
verifySource(node.source, () => {
for (const specifier of node.specifiers) {
if (!vue3ExportNames.has(specifier.local.name)) {
// If there is a name that is not exported from 'vue', it will not be auto-fixed.
return false
}
}
return true
})
}
},
ExportAllDeclaration(node) {
verifySource(
node.source,
// If we change it to `from 'vue'`, it will export more, so it will not be auto-fixed.
() => false
)
}
}
}
}