fix(compiler-sfc): check import source during binding analysation (#6826)

fix #6825
This commit is contained in:
三咲智子 Kevin Deng 2022-11-08 17:07:28 +08:00 committed by GitHub
parent fdc5902cce
commit 4a00fddfed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 13 deletions

View File

@ -356,6 +356,42 @@ defineExpose({ foo: 123 })
content.lastIndexOf(`import { x }`) content.lastIndexOf(`import { x }`)
) )
}) })
describe('import ref/reactive function from other place', () => {
test('import directly', () => {
const { bindings } = compile(`
<script setup>
import { ref, reactive } from './foo'
const foo = ref(1)
const bar = reactive(1)
</script>
`)
expect(bindings).toStrictEqual({
ref: BindingTypes.SETUP_MAYBE_REF,
reactive: BindingTypes.SETUP_MAYBE_REF,
foo: BindingTypes.SETUP_MAYBE_REF,
bar: BindingTypes.SETUP_MAYBE_REF
})
})
test('import w/ alias', () => {
const { bindings } = compile(`
<script setup>
import { ref as _ref, reactive as _reactive } from './foo'
const foo = ref(1)
const bar = reactive(1)
</script>
`)
expect(bindings).toStrictEqual({
_reactive: BindingTypes.SETUP_MAYBE_REF,
_ref: BindingTypes.SETUP_MAYBE_REF,
foo: BindingTypes.SETUP_REF,
bar: BindingTypes.SETUP_REACTIVE_CONST
})
})
})
}) })
// in dev mode, declared bindings are returned as an object from setup() // in dev mode, declared bindings are returned as an object from setup()

View File

@ -129,6 +129,7 @@ export interface SFCScriptCompileOptions {
export interface ImportBinding { export interface ImportBinding {
isType: boolean isType: boolean
imported: string imported: string
local: string
source: string source: string
isFromSetup: boolean isFromSetup: boolean
isUsedInTemplate: boolean isUsedInTemplate: boolean
@ -272,7 +273,6 @@ export function compileScript(
const bindingMetadata: BindingMetadata = {} const bindingMetadata: BindingMetadata = {}
const helperImports: Set<string> = new Set() const helperImports: Set<string> = new Set()
const userImports: Record<string, ImportBinding> = Object.create(null) const userImports: Record<string, ImportBinding> = Object.create(null)
const userImportAlias: Record<string, string> = Object.create(null)
const scriptBindings: Record<string, BindingTypes> = Object.create(null) const scriptBindings: Record<string, BindingTypes> = Object.create(null)
const setupBindings: Record<string, BindingTypes> = Object.create(null) const setupBindings: Record<string, BindingTypes> = Object.create(null)
@ -362,10 +362,6 @@ export function compileScript(
isFromSetup: boolean, isFromSetup: boolean,
needTemplateUsageCheck: boolean needTemplateUsageCheck: boolean
) { ) {
if (source === 'vue' && imported) {
userImportAlias[imported] = local
}
// template usage check is only needed in non-inline mode, so we can skip // template usage check is only needed in non-inline mode, so we can skip
// the work if inlineTemplate is true. // the work if inlineTemplate is true.
let isUsedInTemplate = needTemplateUsageCheck let isUsedInTemplate = needTemplateUsageCheck
@ -382,6 +378,7 @@ export function compileScript(
userImports[local] = { userImports[local] = {
isType, isType,
imported: imported || 'default', imported: imported || 'default',
local,
source, source,
isFromSetup, isFromSetup,
isUsedInTemplate isUsedInTemplate
@ -990,7 +987,7 @@ export function compileScript(
} }
} }
if (node.declaration) { if (node.declaration) {
walkDeclaration(node.declaration, scriptBindings, userImportAlias) walkDeclaration(node.declaration, scriptBindings, userImports)
} }
} else if ( } else if (
(node.type === 'VariableDeclaration' || (node.type === 'VariableDeclaration' ||
@ -999,7 +996,7 @@ export function compileScript(
node.type === 'TSEnumDeclaration') && node.type === 'TSEnumDeclaration') &&
!node.declare !node.declare
) { ) {
walkDeclaration(node, scriptBindings, userImportAlias) walkDeclaration(node, scriptBindings, userImports)
} }
} }
@ -1199,7 +1196,7 @@ export function compileScript(
node.type === 'ClassDeclaration') && node.type === 'ClassDeclaration') &&
!node.declare !node.declare
) { ) {
walkDeclaration(node, setupBindings, userImportAlias) walkDeclaration(node, setupBindings, userImports)
} }
// walk statements & named exports / variable declarations for top level // walk statements & named exports / variable declarations for top level
@ -1654,8 +1651,17 @@ function registerBinding(
function walkDeclaration( function walkDeclaration(
node: Declaration, node: Declaration,
bindings: Record<string, BindingTypes>, bindings: Record<string, BindingTypes>,
userImportAlias: Record<string, string> userImports: Record<string, ImportBinding>
) { ) {
function getUserBinding(name: string) {
const binding = Object.values(userImports).find(
binding => binding.source === 'vue' && binding.imported === name
)
if (binding) return binding.local
else if (!userImports[name]) return name
return undefined
}
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const isConst = node.kind === 'const' const isConst = node.kind === 'const'
// export const foo = ... // export const foo = ...
@ -1669,7 +1675,7 @@ function walkDeclaration(
) )
if (id.type === 'Identifier') { if (id.type === 'Identifier') {
let bindingType let bindingType
const userReactiveBinding = userImportAlias['reactive'] || 'reactive' const userReactiveBinding = getUserBinding('reactive')
if (isCallOf(init, userReactiveBinding)) { if (isCallOf(init, userReactiveBinding)) {
// treat reactive() calls as let since it's meant to be mutable // treat reactive() calls as let since it's meant to be mutable
bindingType = isConst bindingType = isConst
@ -1685,7 +1691,7 @@ function walkDeclaration(
? BindingTypes.SETUP_REACTIVE_CONST ? BindingTypes.SETUP_REACTIVE_CONST
: BindingTypes.SETUP_CONST : BindingTypes.SETUP_CONST
} else if (isConst) { } else if (isConst) {
if (isCallOf(init, userImportAlias['ref'] || 'ref')) { if (isCallOf(init, getUserBinding('ref'))) {
bindingType = BindingTypes.SETUP_REF bindingType = BindingTypes.SETUP_REF
} else { } else {
bindingType = BindingTypes.SETUP_MAYBE_REF bindingType = BindingTypes.SETUP_MAYBE_REF
@ -1982,10 +1988,11 @@ function genRuntimeEmits(emits: Set<string>) {
function isCallOf( function isCallOf(
node: Node | null | undefined, node: Node | null | undefined,
test: string | ((id: string) => boolean) test: string | ((id: string) => boolean) | null | undefined
): node is CallExpression { ): node is CallExpression {
return !!( return !!(
node && node &&
test &&
node.type === 'CallExpression' && node.type === 'CallExpression' &&
node.callee.type === 'Identifier' && node.callee.type === 'Identifier' &&
(typeof test === 'string' (typeof test === 'string'
@ -1994,7 +2001,7 @@ function isCallOf(
) )
} }
function canNeverBeRef(node: Node, userReactiveImport: string): boolean { function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
if (isCallOf(node, userReactiveImport)) { if (isCallOf(node, userReactiveImport)) {
return true return true
} }