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 1bb4edd

Browse files
Add vue/prefer-import-from-vue rule (#1804)
1 parent 86b3b3f commit 1bb4edd

File tree

9 files changed

+776
-3
lines changed

9 files changed

+776
-3
lines changed

‎docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ For example:
352352
| [vue/no-v-text-v-html-on-component](./no-v-text-v-html-on-component.md) | disallow v-text / v-html on component | |
353353
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | |
354354
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
355+
| [vue/prefer-import-from-vue](./prefer-import-from-vue.md) | enforce import from 'vue' instead of import from '@vue/*' | :wrench: |
355356
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: |
356357
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: |
357358
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |

‎docs/rules/prefer-import-from-vue.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/prefer-import-from-vue
5+
description: enforce import from 'vue' instead of import from '@vue/*'
6+
---
7+
# vue/prefer-import-from-vue
8+
9+
> enforce import from 'vue' instead of import from '@vue/*'
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
13+
14+
## :book: Rule Details
15+
16+
This rule aims to use imports from `'vue'` instead of imports from `'@vue/*'`.
17+
18+
Imports from the following modules are almost always wrong. You should import from `vue` instead.
19+
20+
- `@vue/runtime-dom`
21+
- `@vue/runtime-core`
22+
- `@vue/reactivity`
23+
- `@vue/shared`
24+
25+
<eslint-code-block fix :rules="{'vue/prefer-import-from-vue': ['error']}" filename="example.js" language="javascript">
26+
27+
```js
28+
/* ✓ GOOD */
29+
import { createApp, ref, Component } from 'vue'
30+
```
31+
32+
</eslint-code-block>
33+
34+
<eslint-code-block fix :rules="{'vue/prefer-import-from-vue': ['error']}" filename="example.js" language="javascript">
35+
36+
```js
37+
/* ✗ BAD */
38+
import { createApp } from '@vue/runtime-dom'
39+
import { Component } from '@vue/runtime-core'
40+
import { ref } from '@vue/reactivity'
41+
```
42+
43+
</eslint-code-block>
44+
45+
## :wrench: Options
46+
47+
Nothing.
48+
49+
## :mag: Implementation
50+
51+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-import-from-vue.js)
52+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-import-from-vue.js)

‎lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ module.exports = {
157157
'operator-linebreak': require('./rules/operator-linebreak'),
158158
'order-in-components': require('./rules/order-in-components'),
159159
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
160+
'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
160161
'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
161162
'prefer-template': require('./rules/prefer-template'),
162163
'prefer-true-attribute-shorthand': require('./rules/prefer-true-attribute-shorthand'),

‎lib/rules/prefer-import-from-vue.js

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

0 commit comments

Comments
(0)

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