Skip to content

Commit aac2e26

Browse files
committed
fix: escape tildes in paths
Fix posva/unplugin-vue-router#785
1 parent c13a0cc commit aac2e26

File tree

10 files changed

+58
-34
lines changed

10 files changed

+58
-34
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script setup lang="ts">
2+
import { useRoute } from 'vue-router'
3+
4+
const route = useRoute()
5+
</script>
6+
7+
<template>
8+
<h2>{{ route.path }}</h2>
9+
</template>

packages/playground-file-based/src/routes.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ declare module 'vue-router/auto-routes' {
120120
{ when: Extract<Param_date, unknown[]> },
121121
| never
122122
>,
123+
'/it\'s-fine/(lol)': RouteRecordInfo<
124+
'/it\'s-fine/(lol)',
125+
'/it\'s-fine',
126+
Record<never, never>,
127+
Record<never, never>,
128+
| never
129+
>,
123130
'/nested/': RouteRecordInfo<
124131
'/nested/',
125132
'/nested',
@@ -296,6 +303,12 @@ declare module 'vue-router/auto-routes' {
296303
views:
297304
| never
298305
}
306+
'src/pages/it's-fine/(lol).vue': {
307+
routes:
308+
| '/it\'s-fine/(lol)'
309+
views:
310+
| never
311+
}
299312
'src/pages/nested.vue': {
300313
routes:
301314
| '/nested/'

packages/router/src/unplugin/codegen/__snapshots__/generateRouteRecords.spec.ts.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ exports[`generateRouteRecord > does not encode RFC 3986 valid path characters 1`
8282
/* no children */
8383
},
8484
{
85-
path: '/it's-fine',
86-
name: '/it's-fine',
87-
component: () => import('it's-fine.vue'),
85+
path: '/it\\'s-fine',
86+
name: '/it\\'s-fine',
87+
component: () => import('it\\'s-fine.vue'),
8888
/* no children */
8989
},
9090
{

packages/router/src/unplugin/codegen/__snapshots__/generateRouteResolver.spec.ts.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ const __route_2 = normalizeRouteRecord({
2727
})
2828
2929
const __route_3 = normalizeRouteRecord({
30-
name: '/it's-fine',
31-
path: new MatcherPatternPathStatic('/it's-fine'),
30+
name: '/it\\'s-fine',
31+
path: new MatcherPatternPathStatic('/it\\'s-fine'),
3232
components: {
33-
'default': () => import('it's-fine.vue')
33+
'default': () => import('it\\'s-fine.vue')
3434
},
3535
})
3636

packages/router/src/unplugin/codegen/generateRouteFileInfoMap.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { relative } from 'pathe'
22
import type { PrefixTree, TreeNode } from '../core/tree'
3-
import { formatMultilineUnion, stringToStringType } from '../utils'
3+
import { formatMultilineUnion, toStringLiteral } from '../utils'
44

55
export function generateRouteFileInfoMap(
66
node: PrefixTree,
@@ -44,9 +44,9 @@ export function generateRouteFileInfoMap(
4444
`
4545
'${file}': {
4646
routes:
47-
${formatMultilineUnion(routes.sort().map(stringToStringType), 6)}
47+
${formatMultilineUnion(routes.sort().map(toStringLiteral), 6)}
4848
views:
49-
${formatMultilineUnion(views.sort().map(stringToStringType), 6)}
49+
${formatMultilineUnion(views.sort().map(toStringLiteral), 6)}
5050
}`
5151
)
5252
.join('\n')

packages/router/src/unplugin/codegen/generateRouteMap.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
EXPERIMENTAL_generateRouteParams,
66
generateRouteParams,
77
} from './generateRouteParams'
8-
import { pad, formatMultilineUnion, stringToStringType } from '../utils'
8+
import { pad, formatMultilineUnion, toStringLiteral } from '../utils'
99

1010
export function generateRouteNamedMap(
1111
node: TreeNode,
@@ -26,7 +26,7 @@ ${node
2626
(node.value.components.size && node.isNamed()
2727
? pad(
2828
2,
29-
`${stringToStringType(node.name)}: ${generateRouteRecordInfo(node, options, paramParsersMap)},\n`
29+
`${toStringLiteral(node.name)}: ${generateRouteRecordInfo(node, options, paramParsersMap)},\n`
3030
)
3131
: '') +
3232
(node.children.size > 0
@@ -49,8 +49,8 @@ export function generateRouteRecordInfo(
4949
}
5050

5151
const typeParams = [
52-
stringToStringType(node.name),
53-
stringToStringType(node.fullPath),
52+
toStringLiteral(node.name),
53+
toStringLiteral(node.fullPath),
5454
options.experimental.paramParsers
5555
? EXPERIMENTAL_generateRouteParams(node, paramParsers, true)
5656
: generateRouteParams(node, true),
@@ -73,9 +73,7 @@ export function generateRouteRecordInfo(
7373
.sort()
7474
: []
7575

76-
typeParams.push(
77-
formatMultilineUnion(childRouteNames.map(stringToStringType), 4)
78-
)
76+
typeParams.push(formatMultilineUnion(childRouteNames.map(toStringLiteral), 4))
7977

8078
return `RouteRecordInfo<
8179
${typeParams.map(line => pad(4, line)).join(',\n')}

packages/router/src/unplugin/codegen/generateRouteRecords.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getLang } from '@vue-macros/common'
22
import type { TreeNode } from '../core/tree'
33
import { ImportsMap } from '../core/utils'
44
import { type ResolvedOptions } from '../options'
5-
import { pad, stringToStringType } from '../utils'
5+
import { pad, toStringLiteral } from '../utils'
66

77
/**
88
* Generate the route records for the given node.
@@ -57,14 +57,14 @@ ${node
5757

5858
// path
5959
const routeRecord = `${startIndent}{
60-
${indentStr}path: '${node.path}',
60+
${indentStr}path: ${toStringLiteral(node.path)},
6161
${indentStr}${
6262
node.value.components.size
6363
? node.isNamed()
64-
? `name: ${stringToStringType(node.name)},`
64+
? `name: ${toStringLiteral(node.name)},`
6565
: `/* no name */`
6666
: // node.name can still be false and we don't want that to result in string literal 'false'
67-
`/* internal name: ${typeof node.name === 'string' ? stringToStringType(node.name) : node.name} */`
67+
`/* internal name: ${typeof node.name === 'string' ? toStringLiteral(node.name) : node.name} */`
6868
}
6969
${
7070
// component
@@ -126,7 +126,7 @@ function generateRouteRecordComponent(
126126
${files
127127
.map(
128128
([key, path]) =>
129-
`${indentStr + ' '}'${key}': ${generatePageImport(path, importMode, importsMap)}`
129+
`${indentStr + ' '}${toStringLiteral(key)}: ${generatePageImport(path, importMode, importsMap)}`
130130
)
131131
.join(',\n')}
132132
${indentStr}},`
@@ -148,7 +148,7 @@ export function generatePageImport(
148148
const mode =
149149
typeof importMode === 'function' ? importMode(filepath) : importMode
150150
if (mode === 'async') {
151-
return `() => import('${filepath}')`
151+
return `() => import(${toStringLiteral(filepath)})`
152152
}
153153
// mode === 'sync'
154154
// return the name of the import e.g. `_page_0` for `import _page_0 from '...'`

packages/router/src/unplugin/codegen/generateRouteResolver.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getLang } from '@vue-macros/common'
22
import { PrefixTree, type TreeNode } from '../core/tree'
33
import { ImportsMap } from '../core/utils'
44
import { type ResolvedOptions } from '../options'
5-
import { ts } from '../utils'
5+
import { toStringLiteral, ts } from '../utils'
66
import {
77
generatePathParamsOptions,
88
generateParamParserOptions,
@@ -167,10 +167,10 @@ export function generateRouteRecord({
167167
varName,
168168
score: node.score,
169169
})
170-
recordName = `name: '${node.name}',`
170+
recordName = `name: ${toStringLiteral(node.name)},`
171171
} else {
172172
recordName = node.name
173-
? `/* (internal) name: '${node.name}' */`
173+
? `/* (internal) name: ${toStringLiteral(node.name)} */`
174174
: `/* (removed) name: false */`
175175
}
176176

@@ -242,7 +242,7 @@ function generateRouteRecordComponent(
242242
${files
243243
.map(
244244
([key, path]) =>
245-
`${indentStr + ' '}'${key}': ${generatePageImport(path, importMode, importsMap)}`
245+
`${indentStr + ' '}${toStringLiteral(key)}: ${generatePageImport(path, importMode, importsMap)}`
246246
)
247247
.join(',\n')}
248248
${indentStr}},`
@@ -286,7 +286,7 @@ export function generateRouteRecordPath({
286286
${node.isSplat ? 'null,' : '/* trailingSlash */'}
287287
),`
288288
} else {
289-
return `path: new MatcherPatternPathStatic('${node.fullPath}'),`
289+
return `path: new MatcherPatternPathStatic(${toStringLiteral(node.fullPath)}),`
290290
}
291291
}
292292

packages/router/src/unplugin/core/utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
RoutesFolderOptionResolved,
77
_OverridableOption,
88
} from '../options'
9+
import { toStringLiteral } from '../utils'
910

1011
export function warn(
1112
msg: string,
@@ -366,6 +367,8 @@ export class ImportsMap {
366367
for (const [path, imports] of this.map) {
367368
if (!imports.size) continue
368369

370+
const fromImportPath = toStringLiteral(path)
371+
369372
// only one import and it's the default one
370373
if (imports.size === 1) {
371374
// we extract the first and only entry
@@ -374,13 +377,13 @@ export class ImportsMap {
374377
]
375378
// we only care if this is the default import
376379
if (maybeDefault === 'default') {
377-
importStatements += `import ${importName} from '${path}'\n`
380+
importStatements += `import ${importName} from ${fromImportPath}\n`
378381
continue
379382
}
380383
}
381384
importStatements += `import { ${Array.from(imports)
382385
.map(([as, name]) => (as === name ? name : `${name} as ${as}`))
383-
.join(', ')} } from '${path}'\n`
386+
.join(', ')} } from ${fromImportPath}\n`
384387
}
385388

386389
return importStatements

packages/router/src/unplugin/utils/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,16 @@ export function formatMultilineUnion(items: string[], spaces: number): string {
5555
}
5656

5757
/**
58-
* Converts a string value to a TS string literal type.
58+
* Converts a string value to a string literal, escaping as necessary.
5959
*
6060
* @internal
6161
*
6262
* @param str the string to convert to a string type
63-
* @returns The string wrapped in single quotes.
63+
* @returns The string wrapped in single quotes and escaped.
6464
* @example
65-
* stringToStringType('hello') // returns "'hello'"
65+
* toStringLiteral('hello') // returns "'hello'"
66+
* toStringLiteral("it's fine") // returns "'it\'s fine'"
6667
*/
67-
export function stringToStringType(str: string): string {
68-
return `'${str}'`
68+
export function toStringLiteral(str: string): string {
69+
return `'${str.replace(/'/g, "\\'")}'`
6970
}

0 commit comments

Comments
 (0)