fix(compiler-sfc): support nested await statements (#4458)

fix #4448
This commit is contained in:
edison 2021-09-17 04:23:46 +08:00 committed by GitHub
parent 524688bc99
commit ae942cdcd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 261 additions and 53 deletions

View File

@ -96,6 +96,233 @@ export default /*#__PURE__*/ Object.assign(__default__, {
})" })"
`; `;
exports[`SFC compile <script setup> async/await detection expression statement 1`] = `
"import { withAsyncContext as _withAsyncContext } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
;(
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore()
)
return { }
}
}"
`;
exports[`SFC compile <script setup> async/await detection nested await 1`] = `
"import { withAsyncContext as _withAsyncContext } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
;(
([__temp,__restore] = _withAsyncContext(async () => {
return ((
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore(),
__temp
))
})),
__temp = await __temp,
__restore()
)
return { }
}
}"
`;
exports[`SFC compile <script setup> async/await detection nested await 2`] = `
"import { withAsyncContext as _withAsyncContext } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
;(
([__temp,__restore] = _withAsyncContext(async () => {
return (((
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore(),
__temp
)))
})),
__temp = await __temp,
__restore()
)
return { }
}
}"
`;
exports[`SFC compile <script setup> async/await detection nested await 3`] = `
"import { withAsyncContext as _withAsyncContext } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
;(
([__temp,__restore] = _withAsyncContext(async () => {
return ((
([__temp,__restore] = _withAsyncContext(async () => {
return ((
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore(),
__temp
))
})),
__temp = await __temp,
__restore(),
__temp
))
})),
__temp = await __temp,
__restore()
)
return { }
}
}"
`;
exports[`SFC compile <script setup> async/await detection nested statements 1`] = `
"import { withAsyncContext as _withAsyncContext } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
if (ok) { ;(
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore()
) } else { ;(
([__temp,__restore] = _withAsyncContext(() => {
return bar
})),
__temp = await __temp,
__restore()
) }
return { }
}
}"
`;
exports[`SFC compile <script setup> async/await detection ref 1`] = `
"import { withAsyncContext as _withAsyncContext, ref as _ref } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
let a = _ref(1 + ((
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore(),
__temp
)))
return { a }
}
}"
`;
exports[`SFC compile <script setup> async/await detection should ignore await inside functions 1`] = `
"export default {
setup(__props, { expose }) {
expose()
async function foo() { await bar }
return { foo }
}
}"
`;
exports[`SFC compile <script setup> async/await detection should ignore await inside functions 2`] = `
"export default {
setup(__props, { expose }) {
expose()
const foo = async () => { await bar }
return { foo }
}
}"
`;
exports[`SFC compile <script setup> async/await detection should ignore await inside functions 3`] = `
"export default {
setup(__props, { expose }) {
expose()
const obj = { async method() { await bar }}
return { obj }
}
}"
`;
exports[`SFC compile <script setup> async/await detection should ignore await inside functions 4`] = `
"export default {
setup(__props, { expose }) {
expose()
const cls = class Foo { async method() { await bar }}
return { cls }
}
}"
`;
exports[`SFC compile <script setup> async/await detection variable 1`] = `
"import { withAsyncContext as _withAsyncContext } from 'vue'
export default {
async setup(__props, { expose }) {
expose()
let __temp, __restore
const a = 1 + ((
([__temp,__restore] = _withAsyncContext(() => {
return foo
})),
__temp = await __temp,
__restore(),
__temp
))
return { a }
}
}"
`;
exports[`SFC compile <script setup> binding analysis for destructur 1`] = ` exports[`SFC compile <script setup> binding analysis for destructur 1`] = `
"export default { "export default {
setup(__props, { expose }) { setup(__props, { expose }) {

View File

@ -1057,11 +1057,7 @@ const emit = defineEmits(['a', 'b'])
}) })
describe('async/await detection', () => { describe('async/await detection', () => {
function assertAwaitDetection( function assertAwaitDetection(code: string, shouldAsync = true) {
code: string,
expected: string | ((content: string) => boolean),
shouldAsync = true
) {
const { content } = compile(`<script setup>${code}</script>`, { const { content } = compile(`<script setup>${code}</script>`, {
refSugar: true refSugar: true
}) })
@ -1069,70 +1065,41 @@ const emit = defineEmits(['a', 'b'])
expect(content).toMatch(`let __temp, __restore`) expect(content).toMatch(`let __temp, __restore`)
} }
expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`) expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
if (typeof expected === 'string') { assertCode(content)
expect(content).toMatch(expected)
} else {
expect(expected(content)).toBe(true)
}
} }
test('expression statement', () => { test('expression statement', () => {
assertAwaitDetection( assertAwaitDetection(`await foo`)
`await foo`,
`;(([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore())`
)
}) })
test('variable', () => { test('variable', () => {
assertAwaitDetection( assertAwaitDetection(`const a = 1 + (await foo)`)
`const a = 1 + (await foo)`,
`1 + ((([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore(),__temp))`
)
}) })
test('ref', () => { test('ref', () => {
assertAwaitDetection( assertAwaitDetection(`let a = $ref(1 + (await foo))`)
`let a = $ref(1 + (await foo))`, })
`1 + ((([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore(),__temp))`
) test('nested await', () => {
assertAwaitDetection(`await (await foo)`)
assertAwaitDetection(`await ((await foo))`)
assertAwaitDetection(`await (await (await foo))`)
}) })
test('nested statements', () => { test('nested statements', () => {
assertAwaitDetection(`if (ok) { await foo } else { await bar }`, code => { assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
return (
code.includes(
`;(([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore())`
) &&
code.includes(
`;(([__temp,__restore]=_withAsyncContext(()=>(bar))),__temp=await __temp,__restore())`
)
)
})
}) })
test('should ignore await inside functions', () => { test('should ignore await inside functions', () => {
// function declaration // function declaration
assertAwaitDetection( assertAwaitDetection(`async function foo() { await bar }`, false)
`async function foo() { await bar }`,
`await bar`,
false
)
// function expression // function expression
assertAwaitDetection( assertAwaitDetection(`const foo = async () => { await bar }`, false)
`const foo = async () => { await bar }`,
`await bar`,
false
)
// object method // object method
assertAwaitDetection( assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
`const obj = { async method() { await bar }}`,
`await bar`,
false
)
// class method // class method
assertAwaitDetection( assertAwaitDetection(
`const cls = class Foo { async method() { await bar }}`, `const cls = class Foo { async method() { await bar }}`,
`await bar`,
false false
) )
}) })

View File

@ -509,19 +509,33 @@ export function compileScript(
/** /**
* await foo() * await foo()
* --> * -->
* (([__temp, __restore] = withAsyncContext(() => foo())),__temp=await __temp,__restore(),__temp) * (([__temp, __restore] = withAsyncContext(async () => foo())),__temp=await __temp,__restore(),__temp)
*/ */
function processAwait(node: AwaitExpression, isStatement: boolean) { function processAwait(node: AwaitExpression, isStatement: boolean) {
const argumentStart =
node.argument.extra && node.argument.extra.parenthesized
? (node.argument.extra.parenStart as number)
: node.argument.start!
const argumentStr = source.slice(
argumentStart + startOffset,
node.argument.end! + startOffset
)
const containsNestedAwait = /\bawait\b/.test(argumentStr)
s.overwrite( s.overwrite(
node.start! + startOffset, node.start! + startOffset,
node.argument.start! + startOffset, argumentStart + startOffset,
`${isStatement ? `;` : ``}(([__temp,__restore]=${helper( `${isStatement ? `;` : ``}(\n ([__temp,__restore] = ${helper(
`withAsyncContext` `withAsyncContext`
)}(()=>(` )}(${containsNestedAwait ? `async ` : ``}() => {\n return `
) )
s.appendLeft( s.appendLeft(
node.end! + startOffset, node.end! + startOffset,
`))),__temp=await __temp,__restore()${isStatement ? `` : `,__temp`})` `\n })),\n __temp = await __temp,\n __restore()${
isStatement ? `` : `,\n __temp`
}\n)`
) )
} }