diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index d46a700bb42..f7717bbdbe3 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -530,6 +530,7 @@ "createUrlTreeFromSnapshot", "createViewBlueprint", "createViewRef", + "createWildcardMatchResult", "cyclicDependencyError", "deactivateRouteAndItsChildren", "decode", @@ -884,7 +885,6 @@ "markedFeatures", "match", "matchMatrixKeySegments", - "matchParts", "matchQueryParams", "matchSegments", "matchTemplateAttribute", diff --git a/packages/router/src/shared.ts b/packages/router/src/shared.ts index 23147f3facf..23ec9aa8c5e 100644 --- a/packages/router/src/shared.ts +++ b/packages/router/src/shared.ts @@ -117,24 +117,6 @@ export function convertToParamMap(params: Params): ParamMap { return new ParamsAsMap(params); } -function matchParts( - routeParts: string[], - urlSegments: UrlSegment[], - posParams: {[key: string]: UrlSegment}, -): boolean { - for (let i = 0; i < routeParts.length; i++) { - const part = routeParts[i]; - const segment = urlSegments[i]; - const isParameter = part[0] === ':'; - if (isParameter) { - posParams[part.substring(1)] = segment; - } else if (part !== segment.path) { - return false; - } - } - return true; -} - /** * Matches the route configuration (`route`) against the actual URL (`segments`). * @@ -156,57 +138,15 @@ export function defaultUrlMatcher( route: Route, ): UrlMatchResult | null { const parts = route.path!.split('/'); - const wildcardIndex = parts.indexOf('**'); - if (wildcardIndex === -1) { - // No wildcard, use original logic - if (parts.length > segments.length) { - // The actual URL is shorter than the config, no match - return null; - } - if ( - route.pathMatch === 'full' && - (segmentGroup.hasChildren() || parts.length < segments.length) - ) { - // The config is longer than the actual URL but we are looking for a full match, return null - return null; - } - - const posParams: {[key: string]: UrlSegment} = {}; - const consumed = segments.slice(0, parts.length); - if (!matchParts(parts, consumed, posParams)) { - return null; - } - return {consumed, posParams}; - } - - // Path has a wildcard. - if (wildcardIndex !== parts.lastIndexOf('**')) { - // We do not support more than one wildcard segment in the path - return null; - } - - const pre = parts.slice(0, wildcardIndex); - const post = parts.slice(wildcardIndex + 1); - - if (pre.length + post.length > segments.length) { + if (parts.length > segments.length) { // The actual URL is shorter than the config, no match return null; } - if ( - // If the wildcard is not at the end of the path, it must match at least one segment. - // e.g. `foo/**/bar` does not match `foo/bar`. - wildcardIndex > -1 && - pre.length > 0 && - post.length > 0 && - pre.length + post.length === segments.length - ) { - return null; - } if ( route.pathMatch === 'full' && - (segmentGroup.hasChildren() || segments.length > pre.length + post.length) + (segmentGroup.hasChildren() || parts.length < segments.length) ) { // The config is longer than the actual URL but we are looking for a full match, return null return null; @@ -214,18 +154,18 @@ export function defaultUrlMatcher( const posParams: {[key: string]: UrlSegment} = {}; - // Match the segments before the wildcard - if (!matchParts(pre, segments.slice(0, pre.length), posParams)) { - return null; - } - // Match the segments after the wildcard - if (!matchParts(post, segments.slice(segments.length - post.length), posParams)) { - return null; + // Check each config part against the actual URL + for (let index = 0; index < parts.length; index++) { + const part = parts[index]; + const segment = segments[index]; + const isParameter = part[0] === ':'; + if (isParameter) { + posParams[part.substring(1)] = segment; + } else if (part !== segment.path) { + // The actual URL part does not match the config, no match + return null; + } } - // TODO(atscott): put the wildcard segments into a _splat param. - // this would require a breaking change to the UrlMatchResult to allow UrlSegment[] - // since the splat could be multiple segments. - - return {consumed: segments, posParams}; + return {consumed: segments.slice(0, parts.length), posParams}; } diff --git a/packages/router/src/utils/config_matching.ts b/packages/router/src/utils/config_matching.ts index 78bcdea7f38..ac1fdddcf38 100644 --- a/packages/router/src/utils/config_matching.ts +++ b/packages/router/src/utils/config_matching.ts @@ -60,6 +60,10 @@ export function match( route: Route, segments: UrlSegment[], ): MatchResult { + if (route.path === '**') { + return createWildcardMatchResult(segments); + } + if (route.path === '') { if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) { return {...noMatch}; @@ -97,6 +101,16 @@ export function match( }; } +function createWildcardMatchResult(segments: UrlSegment[]): MatchResult { + return { + matched: true, + parameters: segments.length > 0 ? last(segments)!.parameters : {}, + consumedSegments: segments, + remainingSegments: [], + positionalParamSegments: {}, + }; +} + export function split( segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], diff --git a/packages/router/test/recognize.spec.ts b/packages/router/test/recognize.spec.ts index a691c89cc23..9a92ec67983 100644 --- a/packages/router/test/recognize.spec.ts +++ b/packages/router/test/recognize.spec.ts @@ -585,58 +585,6 @@ describe('recognize', () => { const s = await recognize([{path: '**', component: ComponentA}], 'a/b/c/d;a1=11'); checkActivatedRoute(s.root.firstChild!, 'a/b/c/d', {a1: '11'}, ComponentA); }); - - it('should support segments after a wildcard', async () => { - const s = await recognize( - [ - { - path: 'a', - component: ComponentA, - children: [ - { - path: '**/b', - component: ComponentB, - }, - ], - }, - ], - 'a/1/2/b', - ); - const a = s.root.firstChild!; - checkActivatedRoute(a, 'a', {}, ComponentA); - - const wildcard = a.firstChild!; - checkActivatedRoute(wildcard, '1/2/b', {}, ComponentB); - }); - - describe('with segments after', () => { - const recognizer = (url: string) => { - const config = [ - { - path: 'foo/**/bar', - component: ComponentA, - }, - ]; - return recognize(config, url); - }; - - it('matches a url with one segment for the wildcard', async () => { - const s = await recognizer('foo/a/bar'); - checkActivatedRoute(s.root.firstChild!, 'foo/a/bar', {}, ComponentA); - }); - - it('matches a url with multiple segments for the wildcard', async () => { - const s = await recognizer('foo/a/b/c/bar'); - checkActivatedRoute(s.root.firstChild!, 'foo/a/b/c/bar', {}, ComponentA); - }); - it('does not match a url with no segments for the wildcard', async () => { - await expectAsync(recognizer('foo/bar')).toBeRejected(); - }); - - it('does not match a url with a wrong suffix', async () => { - await expectAsync(recognizer('foo/a/b/baz')).toBeRejected(); - }); - }); }); describe('componentless routes', () => {