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(reactivity-transform): ensure that macros comply with the original semantics (#6312) #6944

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 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
Expand Up @@ -10,9 +10,9 @@ const __props_bar = _toRef(__props, 'bar')
const __props_foo = _toRef(__props, 'foo')


console.log((__props_foo))
console.log((__props_bar))
({ foo: __props_foo, baz: __props_bar })
console.log(__props_foo)
console.log(__props_bar)
console.log({ foo: __props_foo, baz: __props_bar })

return () => {}
}
Expand Down
Expand Up @@ -7,11 +7,11 @@ export default {
setup(__props, { expose }) {
expose();

let foo = (ref())
let a = (ref(1))
let b = (shallowRef({
let foo = ref()
let a = ref(1)
let b = shallowRef({
count: 0
}))
})
let c = () => {}
let d

Expand Down Expand Up @@ -65,7 +65,7 @@ exports[`sfc ref transform usage in normal <script> 1`] = `
setup() {
let count = _ref(0)
const inc = () => count.value++
return ({ count })
return { count }
}
}
"
Expand Down
Expand Up @@ -173,14 +173,14 @@ describe('sfc props transform', () => {
const { foo, bar: baz } = defineProps(['foo'])
console.log($$(foo))
console.log($$(baz))
$$({ foo, baz })
console.log($$({ foo, baz }))
</script>
`)
expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
expect(content).toMatch(`console.log((__props_foo))`)
expect(content).toMatch(`console.log((__props_bar))`)
expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
expect(content).toMatch(`console.log(__props_foo)`)
expect(content).toMatch(`console.log(__props_bar)`)
expect(content).toMatch(`{ foo: __props_foo, baz: __props_bar }`)
assertCode(content)
})

Expand Down
Expand Up @@ -22,12 +22,12 @@ describe('sfc ref transform', () => {
expect(content).not.toMatch(`$(ref())`)
expect(content).not.toMatch(`$(ref(1))`)
expect(content).not.toMatch(`$(shallowRef({`)
expect(content).toMatch(`let foo = (ref())`)
expect(content).toMatch(`let a = (ref(1))`)
expect(content).toMatch(`let foo = ref()`)
expect(content).toMatch(`let a = ref(1)`)
expect(content).toMatch(`
let b = (shallowRef({
let b = shallowRef({
count: 0
}))
})
`)
// normal declarations left untouched
expect(content).toMatch(`let c = () => {}`)
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('sfc ref transform', () => {
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
expect(content).toMatch(`let count = _ref(0)`)
expect(content).toMatch(`count.value++`)
expect(content).toMatch(`return ({ count })`)
expect(content).toMatch(`return { count }`)
assertCode(content)
})

Expand Down
Expand Up @@ -3,24 +3,50 @@
exports[`$ unwrapping 1`] = `
"
import { ref, shallowRef } from 'vue'
let foo = (ref())
export let a = (ref(1))
let b = (shallowRef({
let foo = ref()
export let a = ref(1)
let b = shallowRef({
count: 0
}))
})
let c = () => {}
let d
label: var e = (ref())
label: var e = ref()
"
`;

exports[`$$ 1`] = `
"import { ref as _ref } from 'vue'

let a = _ref(1)
const b = (a)
const c = ({ a })
callExternal((a))
const b = a
const c = { a }
callExternal(a)
"
`;

exports[`$$ with some edge cases 1`] = `
"import { ref as _ref } from 'vue'

{
a:(count,a)
}
;((count) + 1)
;([count])
count
console.log(a)
;(a,b)
;(a,b)
count = ( a++ ,b)
count = ()=>(a++,b)
let r1 = _ref(a, (a++,b))
let r2 = { a:(a++,b),b:a }
switch(c){
case d:
a
;(h,f)
break
}
((count++,count,(count,a)))
"
`;

Expand Down Expand Up @@ -59,7 +85,7 @@ exports[`accessing ref binding 1`] = `
exports[`array destructure 1`] = `
"import { ref as _ref, toRef as _toRef } from 'vue'

let n = _ref(1), __$temp_1 = (useFoo()),
let n = _ref(1), __$temp_1 = useFoo(),
a = _toRef(__$temp_1, 0),
b = _toRef(__$temp_1, 1, 1)
console.log(n.value, a.value, b.value)
Expand Down Expand Up @@ -88,7 +114,7 @@ exports[`macro import alias and removal 1`] = `


let a = _ref(1)
const __$temp_1 = (useMouse()),
const __$temp_1 = useMouse(),
x = _toRef(__$temp_1, 'x'),
y = _toRef(__$temp_1, 'y')
"
Expand Down Expand Up @@ -129,9 +155,9 @@ exports[`mutating ref binding 1`] = `
exports[`nested destructure 1`] = `
"import { toRef as _toRef } from 'vue'

let __$temp_1 = (useFoo()),
let __$temp_1 = useFoo(),
b = _toRef(__$temp_1[0].a, 'b')
let __$temp_2 = (useBar()),
let __$temp_2 = useBar(),
d = _toRef(__$temp_2.c, 0),
e = _toRef(__$temp_2.c, 1)
console.log(b.value, d.value, e.value)
Expand Down Expand Up @@ -170,21 +196,21 @@ exports[`nested scopes 1`] = `
a.value++ // if block a
}

return ({ a, b, c, d })
return { a, b, c, d }
}
"
`;

exports[`object destructure 1`] = `
"import { ref as _ref, toRef as _toRef } from 'vue'

let n = _ref(1), __$temp_1 = (useFoo()),
let n = _ref(1), __$temp_1 = useFoo(),
a = _toRef(__$temp_1, 'a'),
c = _toRef(__$temp_1, 'b'),
d = _toRef(__$temp_1, 'd', 1),
f = _toRef(__$temp_1, 'e', 2),
h = _toRef(__$temp_1, g)
let __$temp_2 = (useSomething(() => 1)),
let __$temp_2 = useSomething(() => 1),
foo = _toRef(__$temp_2, 'foo');
console.log(n.value, a.value, c.value, d.value, f.value, h.value, foo.value)
"
Expand All @@ -193,7 +219,7 @@ exports[`object destructure 1`] = `
exports[`object destructure w/ mid-path default values 1`] = `
"import { toRef as _toRef } from 'vue'

const __$temp_1 = (useFoo()),
const __$temp_1 = useFoo(),
b = _toRef((__$temp_1.a || { b: 123 }), 'b')
console.log(b.value)
"
Expand Down
58 changes: 48 additions & 10 deletions packages/reactivity-transform/__tests__/reactivityTransform.spec.ts
Expand Up @@ -30,17 +30,17 @@ test('$ unwrapping', () => {
expect(code).not.toMatch(`$(ref())`)
expect(code).not.toMatch(`$(ref(1))`)
expect(code).not.toMatch(`$(shallowRef({`)
expect(code).toMatch(`let foo = (ref())`)
expect(code).toMatch(`export let a = (ref(1))`)
expect(code).toMatch(`let foo = ref()`)
expect(code).toMatch(`export let a = ref(1)`)
expect(code).toMatch(`
let b = (shallowRef({
let b = shallowRef({
count: 0
}))
})
`)
// normal declarations left untouched
expect(code).toMatch(`let c = () => {}`)
expect(code).toMatch(`let d`)
expect(code).toMatch(`label: var e = (ref())`)
expect(code).toMatch(`label: var e = ref()`)
expect(rootRefs).toStrictEqual(['foo', 'a', 'b', 'e'])
assertCode(code)
})
Expand Down Expand Up @@ -299,9 +299,47 @@ test('$$', () => {
const c = $$({ a })
callExternal($$(a))
`)
expect(code).toMatch(`const b = (a)`)
expect(code).toMatch(`const c = ({ a })`)
expect(code).toMatch(`callExternal((a))`)
expect(code).toMatch(`const b = a`)
expect(code).toMatch(`const c = { a }`)
expect(code).toMatch(`callExternal(a)`)
assertCode(code)
})

test('$$ with some edge cases',()=>{
const { code } = transform(`
{
a:$$(count,a)
}
$$((count) + 1)
$$([count])
$$ (count )
console.log($$($$(a)))
$$(a,b)
$$($$((a,b)))
count = $$( a++ ,b)
count = ()=>$$(a++,b)
let r1 = $ref(a, $$(a++,b))
let r2 = { a:$$(a++,b),b:$$ (a) }
switch(c){
case d:
$$(a)
$$($$(h,f))
break
}
($$(count++,$$(count),$$(count,a)))
`)
expect(code).toMatch("a:(count,a)")
expect(code).toMatch(";((count) + 1)")
expect(code).toMatch(";([count])")
expect(code).toMatch(`;(a,b)`)
expect(code).toMatch(`log(a)`)
expect(code).toMatch(`count = ( a++ ,b)`)
expect(code).toMatch(`()=>(a++,b)`)
expect(code).toMatch(`_ref(a, (a++,b))`)
expect(code).toMatch(`{ a:(a++,b),b:a }`)
expect(code).toMatch(`switch(c)`)
expect(code).toMatch(`;(h,f)`)
expect(code).toMatch(`((count++,count,(count,a)))`)
assertCode(code)
})

Expand Down Expand Up @@ -358,7 +396,7 @@ test('nested scopes', () => {
// inner bar shadowed by function declaration
expect(code).toMatch(`bar() // inner bar`)

expect(code).toMatch(`return ({ a, b, c, d })`)
expect(code).toMatch(`return { a, b, c, d }`)
assertCode(code)
})

Expand Down Expand Up @@ -412,7 +450,7 @@ test('macro import alias and removal', () => {
// should remove imports
expect(code).not.toMatch(`from 'vue/macros'`)
expect(code).toMatch(`let a = _ref(1)`)
expect(code).toMatch(`const __$temp_1 = (useMouse())`)
expect(code).toMatch(`const __$temp_1 = useMouse()`)
assertCode(code)
})

Expand Down
73 changes: 71 additions & 2 deletions packages/reactivity-transform/src/reactivityTransform.ts
Expand Up @@ -284,7 +284,7 @@ export function transformAST(
if (method === convertSymbol) {
// $
// remove macro
s.remove(call.callee.start! + offset, call.callee.end! + offset)
unwrapMacro(call)
if (id.type === 'Identifier') {
// single variable
registerRefBinding(id)
Expand Down Expand Up @@ -550,6 +550,75 @@ export function transformAST(
}
}

/**
* unwrap the code form the macro($、$$), fix #6312 and keep the ideally behavior with the RFC#369
yyx990803 marked this conversation as resolved.
Show resolved Hide resolved
*/
function unwrapMacro(node: CallExpression) {
const argsLength = node.arguments.length
const bracketStart = node.callee.end! + offset

s.remove(node.callee.start! + offset, bracketStart)

if (argsLength === 1) {
const bracketEnd = node.arguments[argsLength - 1].end! + offset
// remove brackets of macros
const argsType = node.arguments[0].type
if (
argsType === 'CallExpression' ||
argsType === 'Identifier' ||
argsType === 'ObjectExpression'
) {
// fix space place
s.remove(node.start! + offset, node.arguments[0].start! + offset)
s.remove(bracketEnd, node.end! + offset)
tuchg marked this conversation as resolved.
Show resolved Hide resolved

// avoid traversal of like `$$(a)`
if (argsType === 'Identifier') {
return
}
} else if (argsType === 'SequenceExpression') {
// fix $$((a,b))
s.remove(bracketStart, bracketStart + 1)
s.remove(bracketEnd, bracketEnd + 1)
}
}

// avoid invalid traversal for $
if (!escapeScope || node.extra) {
return
}

// fix some edge cases for macro $$, eg. $$(a,b)
// resolve nested
if (isIsolateExpression(node)) {
// split the unwrapped code `()()` to `();()`
s.appendLeft(node.callee.start! + offset, ';')
}
}

function isIsolateExpression(node: CallExpression) {
let i = parentStack.length - 1
while (i >= 0) {
const curParent = parentStack[i]
if (curParent.type !== 'ExpressionStatement') {
i--
continue
}
const expression = curParent.expression
if (
expression.type === 'CallExpression' &&
expression.callee.type === 'Identifier' &&
expression.callee.name === escapeSymbol &&
!expression.arguments.includes(node) &&
(i < 1 || parentStack[i - 1].type !== 'LabeledStatement')
) {
return true
}
i--
}
return false
}

// check root scope first
walkScope(ast, true)
;(walk as any)(ast, {
Expand Down Expand Up @@ -623,8 +692,8 @@ export function transformAST(
}

if (callee === escapeSymbol) {
s.remove(node.callee.start! + offset, node.callee.end! + offset)
escapeScope = node
unwrapMacro(node)
}

// TODO remove when out of experimental
Expand Down