Repository: vuejs/eslint-plugin-vue (js)

Issue:
Rule Proposal: disallow imports from @vue/* dependencies
<!--
  Before proposing new rule, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**Please describe what the rule should do:**
<!-- A clear and concise description of what the rule should do. -->
Disallow or warn about imports from `@vue/*` dependencies. Particularly when using Vue CLI and the build target is a library. 

**What category should the rule belong to?**
<!-- (place an "X" next to just one item) -->
[ ] Enforces code style (layout)
[x] Warns about a potential error (problem)
[ ] Suggests an alternate way of doing something (suggestion)
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule should warn about:**

```vue
<!-- Fail or Warn -->
import { computed } from "@vue/reactivity";

import {
  something,
  another,
} from "@vue/compiler-dom";
```

```vue
<!-- Pass -->
import { computed } from "vue";
```

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Full context can be found in [this issue I opened in the core repo](https://github.com/vuejs/core/issues/5327). 

In short, using the `@vue/reactivity` import broke the components I was building for my UI library with Vue CLI and they were failing silently. 


Pull request description:
Add `vue/prefer-import-from-vue` rule
This PR adds `vue/prefer-import-from-vue` rule.
This rule aims to use imports from `'vue'` instead of imports from `'@vue/*'`.

close #1780 
close #1792

Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index b2f19b51..5b2d2229 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -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 |  |
diff --git a/docs/rules/prefer-import-from-vue.md b/docs/rules/prefer-import-from-vue.md
new file mode 100644
index 00000000..e645d916
--- /dev/null
+++ b/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)
diff --git a/lib/index.js b/lib/index.js
index 941de0b5..97ff805d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -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'),
diff --git a/lib/rules/prefer-import-from-vue.js b/lib/rules/prefer-import-from-vue.js
new file mode 100644
index 00000000..1fb4ecf5
--- /dev/null
+++ b/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
+        )
+      }
+    }
+  }
+}
diff --git a/lib/utils/vue3-export-names.json b/lib/utils/vue3-export-names.json
new file mode 100644
index 00000000..53c8fae0
--- /dev/null
+++ b/lib/utils/vue3-export-names.json
@@ -0,0 +1,300 @@
+[
+  "compile",
+  "createApp",
+  "createSSRApp",
+  "defineCustomElement",
+  "defineSSRCustomElement",
+  "hydrate",
+  "render",
+  "Transition",
+  "TransitionGroup",
+  "TransitionGroupProps",
+  "TransitionProps",
+  "useCssModule",
+  "useCssVars",
+  "vModelCheckbox",
+  "vModelDynamic",
+  "vModelRadio",
+  "vModelSelect",
+  "vModelText",
+  "vShow",
+  "VueElement",
+  "VueElementConstructor",
+  "withKeys",
+  "withModifiers",
+  "AllowedComponentProps",
+  "App",
+  "AppConfig",
+  "AppContext",
+  "AsyncComponentLoader",
+  "AsyncComponentOptions",
+  "BaseTransition",
+  "BaseTransitionProps",
+  "callWithAsyncErrorHandling",
+  "callWithErrorHandling",
+  "camelize",
+  "capitalize",
+  "cloneVNode",
+  "Comment",
+  "CompatVue",
+  "Component",
+  "ComponentCustomOptions",
+  "ComponentCustomProperties",
+  "ComponentCustomProps",
+  "ComponentInternalInstance",
+  "ComponentObjectPropsOptions",
+  "ComponentOptions",
+  "ComponentOptionsBase",
+  "ComponentOptionsMixin",
+  "ComponentOptionsWithArrayProps",
+  "ComponentOptionsWithObjectProps",
+  "ComponentOptionsWithoutProps",
+  "ComponentPropsOptions",
+  "ComponentPublicInstance",
+  "computed",
+  "ComputedGetter",
+  "ComputedOptions",
+  "ComputedRef",
+  "ComputedSetter",
+  "ConcreteComponent",
+  "CreateAppFunction",
+  "createBlock",
+  "createCommentVNode",
+  "CreateComponentPublicInstance",
+  "createElementBlock",
+  "createElementVNode",
+  "createHydrationRenderer",
+  "createRenderer",
+  "createSlots",
+  "createStaticVNode",
+  "createTextVNode",
+  "createVNode",
+  "customRef",
+  "CustomRefFactory",
+  "DebuggerEvent",
+  "DebuggerEventExtraInfo",
+  "DebuggerOptions",
+  "DeepReadonly",
+  "defineAsyncComponent",
+  "DefineComponent",
+  "defineComponent",
+  "defineEmits",
+  "defineExpose",
+  "defineProps",
+  "DeprecationTypes",
+  "devtools",
+  "Directive",
+  "DirectiveArguments",
+  "DirectiveBinding",
+  "DirectiveHook",
+  "effect",
+  "EffectScheduler",
+  "EffectScope",
+  "effectScope",
+  "EmitsOptions",
+  "ErrorCodes",
+  "ExtractDefaultPropTypes",
+  "ExtractPropTypes",
+  "Fragment",
+  "FunctionalComponent",
+  "FunctionDirective",
+  "getCurrentInstance",
+  "getCurrentScope",
+  "getTransitionRawChildren",
+  "guardReactiveProps",
+  "h",
+  "handleError",
+  "HMRRuntime",
+  "HydrationRenderer",
+  "initCustomFormatter",
+  "inject",
+  "InjectionKey",
+  "isMemoSame",
+  "isProxy",
+  "isReactive",
+  "isReadonly",
+  "isRef",
+  "isRuntimeOnly",
+  "isShallow",
+  "isVNode",
+  "KeepAlive",
+  "KeepAliveProps",
+  "LegacyConfig",
+  "markRaw",
+  "mergeProps",
+  "MethodOptions",
+  "nextTick",
+  "normalizeClass",
+  "normalizeProps",
+  "normalizeStyle",
+  "ObjectDirective",
+  "ObjectEmitsOptions",
+  "onActivated",
+  "onBeforeMount",
+  "onBeforeUnmount",
+  "onBeforeUpdate",
+  "onDeactivated",
+  "onErrorCaptured",
+  "onMounted",
+  "onRenderTracked",
+  "onRenderTriggered",
+  "onScopeDispose",
+  "onServerPrefetch",
+  "onUnmounted",
+  "onUpdated",
+  "openBlock",
+  "OptionMergeFunction",
+  "Plugin",
+  "popScopeId",
+  "Prop",
+  "PropType",
+  "provide",
+  "proxyRefs",
+  "pushScopeId",
+  "queuePostFlushCb",
+  "reactive",
+  "ReactiveEffect",
+  "ReactiveEffectOptions",
+  "ReactiveEffectRunner",
+  "ReactiveFlags",
+  "readonly",
+  "Ref",
+  "ref",
+  "registerRuntimeCompiler",
+  "Renderer",
+  "RendererElement",
+  "RendererNode",
+  "RendererOptions",
+  "RenderFunction",
+  "renderList",
+  "renderSlot",
+  "resolveComponent",
+  "resolveDirective",
+  "resolveDynamicComponent",
+  "resolveTransitionHooks",
+  "RootHydrateFunction",
+  "RootRenderFunction",
+  "RuntimeCompilerOptions",
+  "setBlockTracking",
+  "setDevtoolsHook",
+  "setTransitionHooks",
+  "SetupContext",
+  "ShallowReactive",
+  "shallowReactive",
+  "shallowReadonly",
+  "ShallowRef",
+  "shallowRef",
+  "ShallowUnwrapRef",
+  "Slot",
+  "Slots",
+  "ssrContextKey",
+  "Static",
+  "stop",
+  "Suspense",
+  "SuspenseBoundary",
+  "SuspenseProps",
+  "Teleport",
+  "TeleportProps",
+  "Text",
+  "toDisplayString",
+  "toHandlerKey",
+  "toHandlers",
+  "toRaw",
+  "ToRef",
+  "toRef",
+  "ToRefs",
+  "toRefs",
+  "TrackOpTypes",
+  "transformVNodeArgs",
+  "TransitionHooks",
+  "TransitionState",
+  "TriggerOpTypes",
+  "triggerRef",
+  "unref",
+  "UnwrapNestedRefs",
+  "UnwrapRef",
+  "useAttrs",
+  "useSlots",
+  "useSSRContext",
+  "useTransitionState",
+  "version",
+  "VNode",
+  "VNodeArrayChildren",
+  "VNodeChild",
+  "VNodeNormalizedChildren",
+  "VNodeProps",
+  "VNodeTypes",
+  "warn",
+  "watch",
+  "WatchCallback",
+  "WatchEffect",
+  "watchEffect",
+  "WatchOptions",
+  "WatchOptionsBase",
+  "watchPostEffect",
+  "WatchSource",
+  "WatchStopHandle",
+  "watchSyncEffect",
+  "withCtx",
+  "withDefaults",
+  "withDirectives",
+  "withMemo",
+  "withScopeId",
+  "WritableComputedOptions",
+  "WritableComputedRef",
+  "CSSProperties",
+  "StyleValue",
+  "HTMLAttributes",
+  "AnchorHTMLAttributes",
+  "AreaHTMLAttributes",
+  "AudioHTMLAttributes",
+  "BaseHTMLAttributes",
+  "BlockquoteHTMLAttributes",
+  "ButtonHTMLAttributes",
+  "CanvasHTMLAttributes",
+  "ColHTMLAttributes",
+  "ColgroupHTMLAttributes",
+  "DataHTMLAttributes",
+  "DetailsHTMLAttributes",
+  "DelHTMLAttributes",
+  "DialogHTMLAttributes",
+  "EmbedHTMLAttributes",
+  "FieldsetHTMLAttributes",
+  "FormHTMLAttributes",
+  "HtmlHTMLAttributes",
+  "IframeHTMLAttributes",
+  "ImgHTMLAttributes",
+  "InsHTMLAttributes",
+  "InputHTMLAttributes",
+  "KeygenHTMLAttributes",
+  "LabelHTMLAttributes",
+  "LiHTMLAttributes",
+  "LinkHTMLAttributes",
+  "MapHTMLAttributes",
+  "MenuHTMLAttributes",
+  "MediaHTMLAttributes",
+  "MetaHTMLAttributes",
+  "MeterHTMLAttributes",
+  "QuoteHTMLAttributes",
+  "ObjectHTMLAttributes",
+  "OlHTMLAttributes",
+  "OptgroupHTMLAttributes",
+  "OptionHTMLAttributes",
+  "OutputHTMLAttributes",
+  "ParamHTMLAttributes",
+  "ProgressHTMLAttributes",
+  "ScriptHTMLAttributes",
+  "SelectHTMLAttributes",
+  "SourceHTMLAttributes",
+  "StyleHTMLAttributes",
+  "TableHTMLAttributes",
+  "TextareaHTMLAttributes",
+  "TdHTMLAttributes",
+  "ThHTMLAttributes",
+  "TimeHTMLAttributes",
+  "TrackHTMLAttributes",
+  "VideoHTMLAttributes",
+  "WebViewHTMLAttributes",
+  "SVGAttributes",
+  "Events"
+]
\ No newline at end of file
diff --git a/tools/update-vue3-export-names.js b/tools/update-vue3-export-names.js
new file mode 100644
index 00000000..fcd31b3e
--- /dev/null
+++ b/tools/update-vue3-export-names.js
@@ -0,0 +1,150 @@
+'use strict'
+
+/*
+ This script updates `lib/utils/vue3-export-names.json` file from vue type.
+ */
+
+const fs = require('fs')
+const path = require('path')
+const https = require('https')
+const { URL } = require('url')
+const tsParser = require('@typescript-eslint/parser')
+
+main()
+
+async function main() {
+  const names = new Set()
+
+  for await (const name of extractExportNames('vue@^3')) {
+    names.add(name)
+  }
+  // Update file.
+  const filePath = path.resolve(
+    __dirname,
+    '../lib/utils/vue3-export-names.json'
+  )
+
+  fs.writeFileSync(filePath, JSON.stringify([...names], null, 2))
+}
+
+async function* extractExportNames(m) {
+  const rootNode = tsParser.parse(await resolveTypeContents(m), {
+    loc: true,
+    range: true
+  })
+  for (const node of rootNode.body) {
+    if (node.type === 'ExportAllDeclaration') {
+      if (node.exported) {
+        yield node.exported.name
+      } else {
+        for await (const name of extractExportNames(node.source.value)) {
+          yield name
+        }
+      }
+    } else if (node.type === 'ExportNamedDeclaration') {
+      if (node.declaration) {
+        if (
+          node.declaration.type === 'ClassDeclaration' ||
+          node.declaration.type === 'ClassExpression' ||
+          node.declaration.type === 'FunctionDeclaration' ||
+          node.declaration.type === 'TSDeclareFunction' ||
+          node.declaration.type === 'TSEnumDeclaration' ||
+          node.declaration.type === 'TSInterfaceDeclaration' ||
+          node.declaration.type === 'TSTypeAliasDeclaration'
+        ) {
+          yield node.declaration.id.name
+        } else if (node.declaration.type === 'VariableDeclaration') {
+          for (const decl of node.declaration.declarations) {
+            yield* extractNamesFromPattern(decl.id)
+          }
+        } else if (node.declaration.type === 'TSModuleDeclaration') {
+          //?
+        }
+      }
+      for (const spec of node.specifiers) {
+        yield spec.exported.name
+      }
+    } else if (node.type === 'ExportDefaultDeclaration') {
+      yield 'default'
+    }
+  }
+}
+
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.ArrayPattern} ArrayPattern
+ * @typedef {import('@typescript-eslint/types').TSESTree.ObjectPattern} ObjectPattern
+ * @typedef {import('@typescript-eslint/types').TSESTree.Identifier} Identifier
+ * @typedef {import('@typescript-eslint/types').TSESTree.AssignmentPattern} AssignmentPattern
+ * @typedef {import('@typescript-eslint/types').TSESTree.MemberExpression} MemberExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.RestElement} RestElement
+ */
+
+/**
+ * @param {Identifier|ArrayPattern|ObjectPattern|AssignmentPattern|MemberExpression|RestElement} node
+ */
+function* extractNamesFromPattern(node) {
+  if (node.type === 'Identifier') {
+    yield node.name
+  } else if (node.type === 'ArrayPattern') {
+    for (const element of node.elements) {
+      yield* extractNamesFromPattern(element)
+    }
+  } else if (node.type === 'ObjectPattern') {
+    for (const prop of node.properties) {
+      if (prop.type === 'Property') {
+        yield prop.key.name
+      } else if (prop.type === 'RestElement') {
+        yield* extractNamesFromPattern(prop)
+      }
+    }
+  } else if (node.type === 'AssignmentPattern') {
+    yield* extractNamesFromPattern(node.left)
+  } else if (node.type === 'RestElement') {
+    yield* extractNamesFromPattern(node.argument)
+  } else if (node.type === 'MemberExpression') {
+    // ?
+  }
+}
+async function resolveTypeContents(m) {
+  const packageJsonText = await httpGet(`https://unpkg.com/${m}/package.json`)
+  const packageJson = JSON.parse(packageJsonText)
+
+  let typesPath =
+    (packageJson.exports &&
+      packageJson.exports['.'] &&
+      packageJson.exports['.'].types) ||
+    packageJson.types
+  if (typesPath.startsWith('./')) {
+    typesPath = typesPath.slice(2)
+  }
+  return await httpGet(`https://unpkg.com/${m}/${typesPath}`)
+}
+
+function httpGet(url) {
+  return new Promise((resolve, reject) => {
+    let result = ''
+    https
+      .get(url, (res) => {
+        if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400) {
+          // redirect
+          let redirectUrl = res.headers.location
+          if (!redirectUrl.startsWith('http')) {
+            const baseUrl = new URL(url)
+            baseUrl.pathname = redirectUrl
+            redirectUrl = String(baseUrl)
+          }
+          resolve(httpGet(redirectUrl))
+          return
+        }
+        res.setEncoding('utf8')
+        res.on('data', (chunk) => {
+          result += String(chunk)
+        })
+        res.on('end', () => {
+          resolve(result)
+        })
+        res.on('error', reject)
+      })
+      .on('error', reject)
+  })
+}
diff --git a/tools/update.js b/tools/update.js
index fc1e2e6b..266aedbc 100644
--- a/tools/update.js
+++ b/tools/update.js
@@ -10,3 +10,7 @@ require('./update-lib-configs')
 require('./update-lib-index')
 require('./update-docs')
 require('./update-docs-rules-index')
+
+if (process.env.IN_VERSION_SCRIPT) {
+  require('./update-vue3-export-names')
+}
diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
index eff4f983..bafff6d2 100644
--- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
@@ -267,7 +267,7 @@ export interface ImportDeclaration extends HasParentNode {
     | ImportDefaultSpecifier
     | ImportNamespaceSpecifier
   )[]
-  source: Literal
+  source: Literal & { value: string }
 }
 export interface ImportSpecifier extends HasParentNode {
   type: 'ImportSpecifier'
@@ -286,10 +286,11 @@ export interface ExportNamedDeclaration extends HasParentNode {
   type: 'ExportNamedDeclaration'
   declaration?: Declaration | null
   specifiers: ExportSpecifier[]
-  source?: Literal | null
+  source?: (Literal & { value: string }) | null
 }
 export interface ExportSpecifier extends HasParentNode {
   type: 'ExportSpecifier'
+  local: Identifier
   exported: Identifier
 }
 export interface ExportDefaultDeclaration extends HasParentNode {
@@ -298,7 +299,7 @@ export interface ExportDefaultDeclaration extends HasParentNode {
 }
 export interface ExportAllDeclaration extends HasParentNode {
   type: 'ExportAllDeclaration'
-  source: Literal
+  source: Literal & { value: string }
   exported: Identifier | null
 }
 export interface ImportExpression extends HasParentNode {


Test patch:
diff --git a/tests/lib/rules/prefer-import-from-vue.js b/tests/lib/rules/prefer-import-from-vue.js
new file mode 100644
index 00000000..64854fd8
--- /dev/null
+++ b/tests/lib/rules/prefer-import-from-vue.js
@@ -0,0 +1,133 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/prefer-import-from-vue')
+
+const tester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('prefer-import-from-vue', rule, {
+  valid: [
+    `import { createApp } from 'vue'`,
+    `import { ref, reactive } from '@vue/composition-api'`,
+    `export { createApp } from 'vue'`,
+    `export * from 'vue'`,
+    `import Foo from 'foo'`,
+    `import { createApp } from 'vue'
+    export { createApp }`
+  ],
+  invalid: [
+    {
+      code: `import { createApp } from '@vue/runtime-dom'`,
+      output: `import { createApp } from 'vue'`,
+      errors: [
+        {
+          message: "Import from 'vue' instead of '@vue/runtime-dom'.",
+          line: 1,
+          column: 27
+        }
+      ]
+    },
+    {
+      code: `import { computed } from '@vue/runtime-core'`,
+      output: `import { computed } from 'vue'`,
+      errors: [
+        {
+          message: "Import from 'vue' instead of '@vue/runtime-core'.",
+          line: 1,
+          column: 26
+        }
+      ]
+    },
+    {
+      code: `import { computed } from '@vue/reactivity'`,
+      output: `import { computed } from 'vue'`,
+      errors: [
+        {
+          message: "Import from 'vue' instead of '@vue/reactivity'.",
+          line: 1,
+          column: 26
+        }
+      ]
+    },
+    {
+      code: `import { normalizeClass } from '@vue/shared'`,
+      output: `import { normalizeClass } from 'vue'`,
+      errors: [
+        {
+          message: "Import from 'vue' instead of '@vue/shared'.",
+          line: 1,
+          column: 32
+        }
+      ]
+    },
+    {
+      code: `import { unknown } from '@vue/reactivity'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/reactivity'."]
+    },
+    {
+      code: `import { unknown } from '@vue/runtime-dom'`,
+      output: `import { unknown } from 'vue'`,
+      errors: ["Import from 'vue' instead of '@vue/runtime-dom'."]
+    },
+    {
+      code: `import * as Foo from '@vue/reactivity'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/reactivity'."]
+    },
+    {
+      code: `import * as Foo from '@vue/runtime-dom'`,
+      output: `import * as Foo from 'vue'`,
+      errors: ["Import from 'vue' instead of '@vue/runtime-dom'."]
+    },
+    {
+      code: `export * from '@vue/reactivity'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/reactivity'."]
+    },
+    {
+      code: `export * from '@vue/runtime-dom'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/runtime-dom'."]
+    },
+    {
+      code: `export { computed } from '@vue/reactivity'`,
+      output: `export { computed } from 'vue'`,
+      errors: ["Import from 'vue' instead of '@vue/reactivity'."]
+    },
+    {
+      code: `export { computed } from '@vue/runtime-dom'`,
+      output: `export { computed } from 'vue'`,
+      errors: ["Import from 'vue' instead of '@vue/runtime-dom'."]
+    },
+    {
+      code: `export { unknown } from '@vue/reactivity'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/reactivity'."]
+    },
+    {
+      code: `export { unknown } from '@vue/runtime-dom'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/runtime-dom'."]
+    },
+    {
+      code: `import unknown from '@vue/reactivity'`,
+      output: null,
+      errors: ["Import from 'vue' instead of '@vue/reactivity'."]
+    },
+    {
+      code: `import unknown from '@vue/runtime-dom'`,
+      output: `import unknown from 'vue'`,
+      errors: ["Import from 'vue' instead of '@vue/runtime-dom'."]
+    }
+  ]
+})