)
})
- test('async/await detection', () => {
- // TODO
- })
-
describe('exports', () => {
test('export const x = ...', () => {
const { content, bindings } = compile(
})
})
+ describe('async/await detection', () => {
+ function assertAwaitDetection(code: string, shouldAsync = true) {
+ const { content } = compile(`<script setup>${code}</script>`)
+ expect(content).toMatch(
+ `export ${shouldAsync ? `async ` : ``}function setup`
+ )
+ }
+
+ test('expression statement', () => {
+ assertAwaitDetection(`await foo`)
+ })
+
+ test('variable', () => {
+ assertAwaitDetection(`const a = 1 + (await foo)`)
+ })
+
+ test('export', () => {
+ assertAwaitDetection(`export const a = 1 + (await foo)`)
+ })
+
+ test('nested statements', () => {
+ assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
+ })
+
+ test('should ignore await inside functions', () => {
+ // function declaration
+ assertAwaitDetection(`export async function foo() { await bar }`, false)
+ // function expression
+ assertAwaitDetection(`const foo = async () => { await bar }`, false)
+ // object method
+ assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
+ // class method
+ assertAwaitDetection(
+ `const cls = class Foo { async method() { await bar }}`,
+ false
+ )
+ })
+ })
+
describe('errors', () => {
test('<script> and <script setup> must have same lang', () => {
expect(
ExpressionStatement,
ArrowFunctionExpression,
ExportSpecifier,
+ Function as FunctionNode,
TSType,
TSTypeLiteral,
TSFunctionType,
const setupExports: Record<string, boolean> = {}
let exportAllIndex = 0
let defaultExport: Node | undefined
- let needDefaultExportRefCheck: boolean = false
+ let needDefaultExportRefCheck = false
+ let hasAwait = false
const checkDuplicateDefaultExport = (node: Node) => {
if (defaultExport) {
// 3. parse <script setup> and walk over top level statements
for (const node of parse(scriptSetup.content, {
- plugins,
+ plugins: [
+ ...plugins,
+ // allow top level await but only inside <script setup>
+ 'topLevelAwait'
+ ],
sourceType: 'module'
}).program.body) {
const start = node.start! + startOffset
recordType(node, declaredTypes)
s.move(start, end, 0)
}
+
+ // walk statements & named exports / variable declarations for top level
+ // await
+ if (
+ node.type === 'VariableDeclaration' ||
+ (node.type === 'ExportNamedDeclaration' &&
+ node.declaration &&
+ node.declaration.type === 'VariableDeclaration') ||
+ node.type.endsWith('Statement')
+ ) {
+ ;(walk as any)(node, {
+ enter(node: Node) {
+ if (isFunction(node)) {
+ this.skip()
+ }
+ if (node.type === 'AwaitExpression') {
+ hasAwait = true
+ }
+ }
+ })
+ }
}
// 4. check default export to make sure it doesn't reference setup scope
// 6. wrap setup code with function.
// export the content of <script setup> as a named export, `setup`.
// this allows `import { setup } from '*.vue'` for testing purposes.
- s.prependLeft(startOffset, `\nexport function setup(${args}) {\n`)
+ s.prependLeft(
+ startOffset,
+ `\nexport ${hasAwait ? `async ` : ``}function setup(${args}) {\n`
+ )
// generate return statement
let returned = `{ ${Object.keys(setupExports).join(', ')} }`
)
)
}
- } else if (
- node.type === 'FunctionDeclaration' ||
- node.type === 'FunctionExpression' ||
- node.type === 'ArrowFunctionExpression'
- ) {
+ } else if (isFunction(node)) {
// walk function expressions and add its arguments to known identifiers
// so that we don't prefix them
node.params.forEach(p =>
)
}
+function isFunction(node: Node): node is FunctionNode {
+ return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
+}
+
/**
* Analyze bindings in normal `<script>`
* Note that `compileScriptSetup` already analyzes bindings as part of its