diff --git a/docs/rules/README.md b/docs/rules/README.md
index b2f19b512..5b2d22293 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 000000000..e645d916a
--- /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: ***This rule has not been released yet.***
+- :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`
+
+
+
+```js
+/* ✓ GOOD */
+import { createApp, ref, Component } from 'vue'
+```
+
+
+
+
+
+```js
+/* ✗ BAD */
+import { createApp } from '@vue/runtime-dom'
+import { Component } from '@vue/runtime-core'
+import { ref } from '@vue/reactivity'
+```
+
+
+
+## :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 941de0b56..97ff805d3 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 000000000..1fb4ecf55
--- /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 000000000..53c8fae02
--- /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/tests/lib/rules/prefer-import-from-vue.js b/tests/lib/rules/prefer-import-from-vue.js
new file mode 100644
index 000000000..64854fd87
--- /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'."]
+ }
+ ]
+})
diff --git a/tools/update-vue3-export-names.js b/tools/update-vue3-export-names.js
new file mode 100644
index 000000000..fcd31b3e3
--- /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 fc1e2e6bd..266aedbcc 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 eff4f983b..bafff6d2b 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 {