Permalink
Browse files

Adding support for escaped parentheses in Route Paths (#4202)

1 parent bc66a96 commit f31a58a4b1c94a6d7df514a13d63e869e209edf5 @sebastiandeutsch sebastiandeutsch committed with timdorr Jan 11, 2017
@@ -13,7 +13,7 @@ React Router uses the concept of nested routes to let you declare nested sets of
A route path is [a string pattern](/docs/Glossary.md#routepattern) that is used to match a URL (or a portion of one). Route paths are interpreted literally, except for the following special symbols:
- `:paramName` – matches a URL segment up to the next `/`, `?`, or `#`. The matched string is called a [param](/docs/Glossary.md#params)
- - `()` – Wraps a portion of the URL that is optional
+ - `()` – Wraps a portion of the URL that is optional. You may escape parentheses if you want to use them in a url using a blackslash \
- `*` – Matches all characters (non-greedy) up to the next character in the pattern, or to the end of the URL if there is none, and creates a `splat` [param](/docs/Glossary.md#params)
- `**` - Matches all characters (greedy) until the next `/`, `?`, or `#` and creates a `splat` [param](/docs/Glossary.md#params)
@@ -22,6 +22,7 @@ A route path is [a string pattern](/docs/Glossary.md#routepattern) that is used
<Route path="/hello(/:name)"> // matches /hello, /hello/michael, and /hello/ryan
<Route path="/files/*.*"> // matches /files/hello.jpg and /files/hello.html
<Route path="/**/*.jpg"> // matches /files/hello.jpg and /files/path/to/file.jpg
+<Route path="/hello\\(:name\\)"> // matches /hello(michael)
```
If a route uses a relative `path`, it builds upon the accumulated `path` of its ancestors. Nested routes may opt-out of this behavior by [using an absolute `path`](RouteConfiguration.md#decoupling-the-ui-from-the-url).
@@ -9,7 +9,7 @@ function _compilePattern(pattern) {
const paramNames = []
const tokens = []
- let match, lastIndex = 0, matcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|\*\*|\*|\(|\)/g
+ let match, lastIndex = 0, matcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|\*\*|\*|\(|\)|\\\(|\\\)/g
while ((match = matcher.exec(pattern))) {
if (match.index !== lastIndex) {
tokens.push(pattern.slice(lastIndex, match.index))
@@ -29,6 +29,10 @@ function _compilePattern(pattern) {
regexpSource += '(?:'
} else if (match[0] === ')') {
regexpSource += ')?'
+ } else if (match[0] === '\\(') {
+ regexpSource += '\\('
+ } else if (match[0] === '\\)') {
+ regexpSource += '\\)'
}
tokens.push(match[0])
@@ -177,6 +181,10 @@ export function formatPattern(pattern, params) {
parenHistory[parenCount - 1] += parenText
else
pathname += parenText
+ } else if (token === '\\(') {
+ pathname += '('
+ } else if (token === '\\)') {
+ pathname += ')'
} else if (token.charAt(0) === ':') {
paramName = token.substring(1)
paramValue = params[paramName]
@@ -159,6 +159,22 @@ describe('formatPattern', function () {
})
})
+ describe('and a param is parentheses escaped', function () {
+ const pattern = '/comments\\(:id\\)'
+
+ it('returns the correct path when param is supplied', function () {
+ expect(formatPattern(pattern, { id:'123' })).toEqual('/comments(123)')
+ })
+ })
+
+ describe('and a param is parentheses escaped with additional param', function () {
+ const pattern = '/comments\\(:id\\)/:mode'
+
+ it('returns the correct path when param is supplied', function () {
+ expect(formatPattern(pattern, { id:'123', mode: 'edit' })).toEqual('/comments(123)/edit')
+ })
+ })
+
describe('and all params are present', function () {
it('returns the correct path', function () {
expect(formatPattern(pattern, { id: 'abc' })).toEqual('/comments/abc/edit')
@@ -179,4 +179,36 @@ describe('getParams', function () {
})
})
})
+
+ describe('and the pattern is parentheses escaped', function () {
+ const pattern = '/comments\\(test\\)'
+
+ describe('and the path matches with supplied param', function () {
+ it('returns an object with the params', function () {
+ expect(getParams(pattern, '/comments(test)')).toEqual({ })
+ })
+ })
+
+ describe('and the path does not match without parentheses', function () {
+ it('returns an object with an undefined param', function () {
+ expect(getParams(pattern, '/commentstest')).toBe(null)
+ })
+ })
+ })
+
+ describe('and the pattern is parentheses escaped', function () {
+ const pattern = '/comments\\(:id\\)'
+
+ describe('and the path matches with supplied param', function () {
+ it('returns an object with the params', function () {
+ expect(getParams(pattern, '/comments(123)')).toEqual({ id: '123' })
+ })
+ })
+
+ describe('and the path does not match without parentheses', function () {
+ it('returns an object with an undefined param', function () {
+ expect(getParams(pattern, '/commentsedit')).toBe(null)
+ })
+ })
+ })
})

0 comments on commit f31a58a

Please sign in to comment.