diff --git a/packages/playground-file-based/src/pages/[...path].vue b/packages/playground-file-based/src/pages/[...path].vue index c8565ce6a..be501e5e6 100644 --- a/packages/playground-file-based/src/pages/[...path].vue +++ b/packages/playground-file-based/src/pages/[...path].vue @@ -60,6 +60,10 @@ definePage({ }, }, }, + beforeEnter(to) { + to.params.active satisfies boolean + to.params.other satisfies boolean | undefined + }, }) diff --git a/packages/router/src/volar/entries/sfc-typed-router.ts b/packages/router/src/volar/entries/sfc-typed-router.ts index 26d889054..ee81fda1a 100644 --- a/packages/router/src/volar/entries/sfc-typed-router.ts +++ b/packages/router/src/volar/entries/sfc-typed-router.ts @@ -39,6 +39,18 @@ const plugin: VueLanguagePlugin<{ options?: { rootDir?: string } }> = ({ }, } + const defineNames = new Set(['definePage', 'definePageMeta']) + const methodKeys = new Set([ + // vue-router + 'beforeEnter', + 'redirect', + // nuxt + 'key', + 'middleware', + 'scrollToTop', + 'validate', + ]) + return { version: 2.1, resolveEmbeddedCode(fileName, sfc, embeddedCode) { @@ -60,6 +72,67 @@ const plugin: VueLanguagePlugin<{ options?: { rootDir?: string } }> = ({ function visit(node: ts.Node) { if ( + ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + defineNames.has(node.expression.text) && + node.arguments.length >= 1 && + ts.isObjectLiteralExpression(node.arguments[0]) + ) { + for (const prop of node.arguments[0].properties) { + if ( + !prop.name || + !ts.isIdentifier(prop.name) || + !methodKeys.has(prop.name.text) + ) { + continue + } + + const targetArg = ( + ts.isPropertyAssignment(prop) && + ts.isFunctionLike(prop.initializer) + ? prop.initializer + : ts.isMethodDeclaration(prop) + ? prop + : undefined + )?.parameters[0] + + if ( + targetArg && + ts.isIdentifier(targetArg.name) && + !targetArg.type && + !targetArg.dotDotDotToken + ) { + const source = sfc.scriptSetup!.name + const { start, end } = getStartEnd( + targetArg.name, + sfc.scriptSetup!.ast + ) + const token = Symbol() + replaceSourceRange( + embeddedCode.content, + source, + start, + start, + [ + ``, + source, + start, + { __combineToken: token, verification: true }, + ], + `{ key: ` + ) + replaceSourceRange( + embeddedCode.content, + source, + end, + end, + ` = {} as any }`, + [``, source, start, { __combineToken: token }], + `: Record & { key?: ReturnType }` + ) + } + } + } else if ( ts.isCallExpression(node) && ts.isIdentifier(node.expression) && ts.idText(node.expression) === 'useRoute' &&