Skip to content

Commit

Permalink
Add vue/prefer-import-from-vue rule (#1804)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Feb 22, 2022
1 parent 86b3b3f commit 1bb4edd
Show file tree
Hide file tree
Showing 9 changed files with 776 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -352,6 +352,7 @@ For example:
| [vue/no-v-text-v-html-on-component](./no-v-text-v-html-on-component.md) | disallow v-text / v-html on component | |
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
| [vue/prefer-import-from-vue](./prefer-import-from-vue.md) | enforce import from 'vue' instead of import from '@vue/*' | :wrench: |
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: |
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: |
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | |
Expand Down
52 changes: 52 additions & 0 deletions docs/rules/prefer-import-from-vue.md
@@ -0,0 +1,52 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/prefer-import-from-vue
description: enforce import from 'vue' instead of import from '@vue/*'
---
# vue/prefer-import-from-vue

> enforce import from 'vue' instead of import from '@vue/*'
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :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.

## :book: Rule Details

This rule aims to use imports from `'vue'` instead of imports from `'@vue/*'`.

Imports from the following modules are almost always wrong. You should import from `vue` instead.

- `@vue/runtime-dom`
- `@vue/runtime-core`
- `@vue/reactivity`
- `@vue/shared`

<eslint-code-block fix :rules="{'vue/prefer-import-from-vue': ['error']}" filename="example.js" language="javascript">

```js
/* ✓ GOOD */
import { createApp, ref, Component } from 'vue'
```

</eslint-code-block>

<eslint-code-block fix :rules="{'vue/prefer-import-from-vue': ['error']}" filename="example.js" language="javascript">

```js
/* ✗ BAD */
import { createApp } from '@vue/runtime-dom'
import { Component } from '@vue/runtime-core'
import { ref } from '@vue/reactivity'
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-import-from-vue.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-import-from-vue.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -157,6 +157,7 @@ module.exports = {
'operator-linebreak': require('./rules/operator-linebreak'),
'order-in-components': require('./rules/order-in-components'),
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
'prefer-template': require('./rules/prefer-template'),
'prefer-true-attribute-shorthand': require('./rules/prefer-true-attribute-shorthand'),
Expand Down
131 changes: 131 additions & 0 deletions lib/rules/prefer-import-from-vue.js
@@ -0,0 +1,131 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const vue3ExportNames = new Set(require('../utils/vue3-export-names.json'))

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

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) {
if (specifier.type === 'ImportDefaultSpecifier') {
yield 'default'
} else if (specifier.type === 'ImportNamespaceSpecifier') {
yield null // all
} else if (specifier.type === 'ImportSpecifier') {
yield specifier.imported.name
}
}
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: "enforce import from 'vue' instead of import from '@vue/*'",
// TODO We will change it in the next major version.
// categories: ['vue3-essential'],
categories: undefined,
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) {
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
)
}
}
}
}

0 comments on commit 1bb4edd

Please sign in to comment.