Revert "feat(router): Support wildcard params with segments trailing"

This reverts commit 0a0cc27aea.
Causes internal failures with route matching. Should investigate and add
tests to prevent future regressions.
This commit is contained in:
Andrew Scott
2025-10-28 10:25:08 -07:00
committed by Kristiyan Kostadinov
parent 3032332da0
commit 5a93eeb6c0
4 changed files with 29 additions and 127 deletions
@@ -530,6 +530,7 @@
"createUrlTreeFromSnapshot",
"createViewBlueprint",
"createViewRef",
"createWildcardMatchResult",
"cyclicDependencyError",
"deactivateRouteAndItsChildren",
"decode",
@@ -884,7 +885,6 @@
"markedFeatures",
"match",
"matchMatrixKeySegments",
"matchParts",
"matchQueryParams",
"matchSegments",
"matchTemplateAttribute",
+14 -74
View File
@@ -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};
}
@@ -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[],
-52
View File
@@ -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', () => {