Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler-sfc): check source for user imported bindings #6826

Merged
merged 2 commits into from Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/compiler-sfc/__tests__/compileScript.spec.ts
Expand Up @@ -341,6 +341,42 @@ defineExpose({ foo: 123 })
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()
Expand Down
33 changes: 20 additions & 13 deletions packages/compiler-sfc/src/compileScript.ts
Expand Up @@ -129,6 +129,7 @@ export interface SFCScriptCompileOptions {
export interface ImportBinding {
isType: boolean
imported: string
local: string
source: string
isFromSetup: boolean
isUsedInTemplate: boolean
Expand Down Expand Up @@ -272,7 +273,6 @@ export function compileScript(
const bindingMetadata: BindingMetadata = {}
const helperImports: Set<string> = new Set()
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 setupBindings: Record<string, BindingTypes> = Object.create(null)

Expand Down Expand Up @@ -362,10 +362,6 @@ export function compileScript(
isFromSetup: boolean,
needTemplateUsageCheck: boolean
) {
if (source === 'vue' && imported) {
userImportAlias[imported] = local
}

// template usage check is only needed in non-inline mode, so we can skip
// the work if inlineTemplate is true.
let isUsedInTemplate = needTemplateUsageCheck
Expand All @@ -382,6 +378,7 @@ export function compileScript(
userImports[local] = {
isType,
imported: imported || 'default',
local,
source,
isFromSetup,
isUsedInTemplate
Expand Down Expand Up @@ -990,7 +987,7 @@ export function compileScript(
}
}
if (node.declaration) {
walkDeclaration(node.declaration, scriptBindings, userImportAlias)
walkDeclaration(node.declaration, scriptBindings, userImports)
}
} else if (
(node.type === 'VariableDeclaration' ||
Expand All @@ -999,7 +996,7 @@ export function compileScript(
node.type === 'TSEnumDeclaration') &&
!node.declare
) {
walkDeclaration(node, scriptBindings, userImportAlias)
walkDeclaration(node, scriptBindings, userImports)
}
}

Expand Down Expand Up @@ -1199,7 +1196,7 @@ export function compileScript(
node.type === 'ClassDeclaration') &&
!node.declare
) {
walkDeclaration(node, setupBindings, userImportAlias)
walkDeclaration(node, setupBindings, userImports)
}

// walk statements & named exports / variable declarations for top level
Expand Down Expand Up @@ -1654,8 +1651,17 @@ function registerBinding(
function walkDeclaration(
node: Declaration,
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') {
const isConst = node.kind === 'const'
// export const foo = ...
Expand All @@ -1669,7 +1675,7 @@ function walkDeclaration(
)
if (id.type === 'Identifier') {
let bindingType
const userReactiveBinding = userImportAlias['reactive'] || 'reactive'
const userReactiveBinding = getUserBinding('reactive')
if (isCallOf(init, userReactiveBinding)) {
// treat reactive() calls as let since it's meant to be mutable
bindingType = isConst
Expand All @@ -1685,7 +1691,7 @@ function walkDeclaration(
? BindingTypes.SETUP_REACTIVE_CONST
: BindingTypes.SETUP_CONST
} else if (isConst) {
if (isCallOf(init, userImportAlias['ref'] || 'ref')) {
if (isCallOf(init, getUserBinding('ref'))) {
bindingType = BindingTypes.SETUP_REF
} else {
bindingType = BindingTypes.SETUP_MAYBE_REF
Expand Down Expand Up @@ -1982,10 +1988,11 @@ function genRuntimeEmits(emits: Set<string>) {

function isCallOf(
node: Node | null | undefined,
test: string | ((id: string) => boolean)
test: string | ((id: string) => boolean) | null | undefined
): node is CallExpression {
return !!(
node &&
test &&
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(typeof test === 'string'
Expand All @@ -1994,7 +2001,7 @@ function isCallOf(
)
}

function canNeverBeRef(node: Node, userReactiveImport: string): boolean {
function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
if (isCallOf(node, userReactiveImport)) {
return true
}
Expand Down