Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions modules/PatternUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,28 +82,57 @@ export function compilePattern(pattern) {
* - paramValues
*/
export function matchPattern(pattern, pathname) {
// Make leading slashes consistent between pattern and pathname.
if (pattern.charAt(0) !== '/') {
pattern = `/${pattern}`
}
if (pathname.charAt(0) !== '/') {
pathname = `/${pathname}`
}

let { regexpSource, paramNames, tokens } = compilePattern(pattern)

regexpSource += '/*' // Ignore trailing slashes

// Special-case patterns like '*' for catch-all routes.
const captureRemaining = tokens[tokens.length - 1] !== '*'

if (captureRemaining)
if (captureRemaining) {
// This will match newlines in the remaining path.
regexpSource += '([\\s\\S]*?)'
}

const match = pathname.match(new RegExp('^' + regexpSource + '$', 'i'))

let remainingPathname, paramValues
if (match != null) {
paramValues = Array.prototype.slice.call(match, 1).map(function (v) {
return v != null ? decodeURIComponent(v) : v
})

let matchedPath
if (captureRemaining) {
remainingPathname = paramValues.pop()
remainingPathname = match.pop()
matchedPath =
match[0].substr(0, match[0].length - remainingPathname.length)
} else {
remainingPathname = pathname.replace(match[0], '')
// If this matched at all, then the match was the entire pathname.
matchedPath = match[0]
remainingPathname = ''
}

// Ensure we actually match at a path boundary.
if (remainingPathname && remainingPathname.charAt(0) !== '/') {
// This depends on the leading slash getting added to pathname above to
// work in all cases.
if (!matchedPath || matchedPath.charAt(matchedPath.length - 1) !== '/') {
return {
remainingPathname: null,
paramNames,
paramValues: null
}
}
}

paramValues = match.slice(1).map(
v => v != null ? decodeURIComponent(v) : v
)
} else {
remainingPathname = paramValues = null
}
Expand Down
27 changes: 20 additions & 7 deletions modules/isActive.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,39 @@ function deepEqual(a, b) {
}

function paramsAreActive(paramNames, paramValues, activeParams) {
// FIXME: This doesn't work on repeated params in activeParams.
return paramNames.every(function (paramName, index) {
return String(paramValues[index]) === String(activeParams[paramName])
})
}

function getMatchingRoute(pathname, activeRoutes, activeParams) {
let route, pattern, basename = ''
let route, pattern
let remainingPathname = pathname, paramNames = [], paramValues = []

for (let i = 0, len = activeRoutes.length; i < len; ++i) {
route = activeRoutes[i]
pattern = route.path || ''

if (pattern.charAt(0) !== '/')
pattern = basename.replace(/\/*$/, '/') + pattern // Relative paths build on the parent's path.
if (pattern.charAt(0) === '/') {
remainingPathname = pathname
paramNames = []
paramValues = []
}

let { remainingPathname, paramNames, paramValues } = matchPattern(pattern, pathname)
if (remainingPathname !== null) {
const matched = matchPattern(pattern, remainingPathname)
remainingPathname = matched.remainingPathname
paramNames = [ ...paramNames, ...matched.paramNames ]
paramValues = [ ...paramValues, ...matched.paramValues ]
}

if (remainingPathname === '' && route.path && paramsAreActive(paramNames, paramValues, activeParams))
if (
remainingPathname === '' &&
route.path &&
paramsAreActive(paramNames, paramValues, activeParams)
)
return route

basename = pattern
}

return null
Expand Down
84 changes: 51 additions & 33 deletions modules/matchRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ function getIndexRoute(route, location, callback) {
}

function assignParams(params, paramNames, paramValues) {
return paramNames.reduceRight(function (params, paramName, index) {
return paramNames.reduce(function (params, paramName, index) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a leftover from a direction I ended up abandoning, but it seems like it simplifies this function a bit to not use reduceRight.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduceRight uses the last params first, so subsequent params that are named the same thing appear after previous values in the params array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems simpler but equivalent to use reduce and push over reduceRight and unshift, though.

const paramValue = paramValues && paramValues[index]

if (Array.isArray(params[paramName])) {
params[paramName].unshift(paramValue)
params[paramName].push(paramValue)
} else if (paramName in params) {
params[paramName] = [ paramValue, params[paramName] ]
params[paramName] = [ params[paramName], paramValue ]
} else {
params[paramName] = paramValue
}
Expand All @@ -63,34 +63,46 @@ function createParams(paramNames, paramValues) {
return assignParams({}, paramNames, paramValues)
}

function matchRouteDeep(basename, route, location, callback) {
function matchRouteDeep(
route, location, remainingPathname, paramNames, paramValues, callback
) {
let pattern = route.path || ''

if (pattern.charAt(0) !== '/')
pattern = basename.replace(/\/*$/, '/') + pattern // Relative paths build on the parent's path.
if (pattern.charAt(0) === '/') {
remainingPathname = location.pathname
paramNames = []
paramValues = []
}

const { remainingPathname, paramNames, paramValues } = matchPattern(pattern, location.pathname)
const isExactMatch = remainingPathname === ''
if (remainingPathname !== null) {
const matched = matchPattern(pattern, remainingPathname)
remainingPathname = matched.remainingPathname
paramNames = [ ...paramNames, ...matched.paramNames ]
paramValues = [ ...paramValues, ...matched.paramValues ]

if (isExactMatch && route.path) {
const match = {
routes: [ route ],
params: createParams(paramNames, paramValues)
}
if (remainingPathname === '' && route.path) {
const match = {
routes: [ route ],
params: createParams(paramNames, paramValues)
}

getIndexRoute(route, location, function (error, indexRoute) {
if (error) {
callback(error)
} else {
if (Array.isArray(indexRoute))
match.routes.push(...indexRoute)
else if (indexRoute)
match.routes.push(indexRoute)
getIndexRoute(route, location, function (error, indexRoute) {
if (error) {
callback(error)
} else {
if (Array.isArray(indexRoute))
match.routes.push(...indexRoute)
else if (indexRoute)
match.routes.push(indexRoute)

callback(null, match)
}
})
} else if (remainingPathname != null || route.childRoutes) {
callback(null, match)
}
})
return
}
}

if (remainingPathname != null || route.childRoutes) {
// Either a) this route matched at least some of the path or b)
// we don't have to load this route's children asynchronously. In
// either case continue checking for matches in the subtree.
Expand All @@ -109,7 +121,7 @@ function matchRouteDeep(basename, route, location, callback) {
} else {
callback()
}
}, pattern)
}, remainingPathname, paramNames, paramValues)
} else {
callback()
}
Expand All @@ -130,15 +142,21 @@ function matchRouteDeep(basename, route, location, callback) {
* Note: This operation may finish synchronously if no routes have an
* asynchronous getChildRoutes method.
*/
function matchRoutes(routes, location, callback, basename='') {
function matchRoutes(
routes, location, callback,
remainingPathname=location.pathname, paramNames=[], paramValues=[]
) {
loopAsync(routes.length, function (index, next, done) {
matchRouteDeep(basename, routes[index], location, function (error, match) {
if (error || match) {
done(error, match)
} else {
next()
matchRouteDeep(
routes[index], location, remainingPathname, paramNames, paramValues,
function (error, match) {
if (error || match) {
done(error, match)
} else {
next()
}
}
})
)
}, callback)
}

Expand Down