|
| 1 | +import { relative } from 'pathe' |
| 2 | +import type { VueLanguagePlugin } from '@vue/language-core' |
| 3 | +import { replaceSourceRange, toString } from 'muggle-string' |
| 4 | +import { augmentVlsCtx } from '../utils/augment-vls-ctx' |
| 5 | +import type ts from 'typescript' |
| 6 | + |
| 7 | +/* |
| 8 | + Future ideas: |
| 9 | + - Enhance typing of `onBeforeRouteUpdate() to and from parameters |
| 10 | + - Enhance typing of `onBeforeRouteLeave() from parameter |
| 11 | + - Enhance typing of `<RouterView>` |
| 12 | + - Typed `name` attribute for named views |
| 13 | + - Typed `route` slot prop when using `<RouterView v-slot="{route}">` |
| 14 | + - (low priority) Enhance typing of `to` route in `beforeEnter` route guards defined in `definePage` |
| 15 | +*/ |
| 16 | + |
| 17 | +const plugin: VueLanguagePlugin = ({ |
| 18 | + compilerOptions, |
| 19 | + modules: { typescript: ts }, |
| 20 | +}) => { |
| 21 | + const RE = { |
| 22 | + DOLLAR_ROUTE: { |
| 23 | + /** |
| 24 | + * When using `$route` in a template, it is referred |
| 25 | + * to as `__VLS_ctx.$route` in the virtual file. |
| 26 | + */ |
| 27 | + VLS_CTX: /\b__VLS_ctx.\$route\b/g, |
| 28 | + }, |
| 29 | + } |
| 30 | + |
| 31 | + return { |
| 32 | + version: 2.1, |
| 33 | + resolveEmbeddedCode(fileName, sfc, embeddedCode) { |
| 34 | + if (!embeddedCode.id.startsWith('script_')) { |
| 35 | + return |
| 36 | + } |
| 37 | + |
| 38 | + // TODO: Do we want to apply this to EVERY .vue file or only to components that the user wrote themselves? |
| 39 | + |
| 40 | + // NOTE: this might not work if different from the root passed to VueRouter unplugin |
| 41 | + const relativeFilePath = compilerOptions.rootDir |
| 42 | + ? relative(compilerOptions.rootDir, fileName) |
| 43 | + : fileName |
| 44 | + |
| 45 | + const useRouteNameType = `import('vue-router/auto-routes')._RouteNamesForFilePath<'${relativeFilePath}'>` |
| 46 | + const useRouteNameTypeParam = `<${useRouteNameType}>` |
| 47 | + |
| 48 | + if (sfc.scriptSetup) { |
| 49 | + visit(sfc.scriptSetup.ast) |
| 50 | + } |
| 51 | + |
| 52 | + function visit(node: ts.Node) { |
| 53 | + if ( |
| 54 | + ts.isCallExpression(node) && |
| 55 | + ts.isIdentifier(node.expression) && |
| 56 | + node.expression.text === 'useRoute' && |
| 57 | + !node.typeArguments && |
| 58 | + !node.arguments.length |
| 59 | + ) { |
| 60 | + if (!sfc.scriptSetup!.lang.startsWith('js')) { |
| 61 | + replaceSourceRange( |
| 62 | + embeddedCode.content, |
| 63 | + sfc.scriptSetup!.name, |
| 64 | + node.expression.end, |
| 65 | + node.expression.end, |
| 66 | + useRouteNameTypeParam |
| 67 | + ) |
| 68 | + } else { |
| 69 | + const start = node.getStart(sfc.scriptSetup!.ast) |
| 70 | + replaceSourceRange( |
| 71 | + embeddedCode.content, |
| 72 | + sfc.scriptSetup!.name, |
| 73 | + start, |
| 74 | + start, |
| 75 | + `(` |
| 76 | + ) |
| 77 | + replaceSourceRange( |
| 78 | + embeddedCode.content, |
| 79 | + sfc.scriptSetup!.name, |
| 80 | + node.end, |
| 81 | + node.end, |
| 82 | + ` as ReturnType<typeof useRoute${useRouteNameTypeParam}>)` |
| 83 | + ) |
| 84 | + } |
| 85 | + } else { |
| 86 | + ts.forEachChild(node, visit) |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + const contentStr = toString(embeddedCode.content) |
| 91 | + |
| 92 | + const vlsCtxAugmentations: string[] = [] |
| 93 | + |
| 94 | + // Augment `__VLS_ctx.$route` to override the typings of `$route` in template blocks |
| 95 | + if (contentStr.match(RE.DOLLAR_ROUTE.VLS_CTX)) { |
| 96 | + vlsCtxAugmentations.push( |
| 97 | + `{} as { $route: ReturnType<typeof import('vue-router').useRoute${useRouteNameTypeParam}> }` |
| 98 | + ) |
| 99 | + } |
| 100 | + |
| 101 | + // We can try augmenting the types for `RouterView` below. |
| 102 | + // if (contentStr.includes(`__VLS_WithComponent<'RouterView', __VLS_LocalComponents`)) { |
| 103 | + // vlsCtxAugmentations.push(`RouterView: 'test';`) |
| 104 | + // } |
| 105 | + |
| 106 | + if (vlsCtxAugmentations.length) { |
| 107 | + augmentVlsCtx(embeddedCode.content, vlsCtxAugmentations) |
| 108 | + } |
| 109 | + }, |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +export default plugin |
0 commit comments