736 lines
23 KiB
Plaintext
736 lines
23 KiB
Plaintext
import { last } from "./utils.js";
|
|
import { invariant } from "./invariant.js";
|
|
import { createLRUCache } from "./lru-cache.js";
|
|
const SEGMENT_TYPE_INDEX = 4;
|
|
const SEGMENT_TYPE_PATHLESS = 5;
|
|
function getOpenAndCloseBraces(part) {
|
|
const openBrace = part.indexOf("{");
|
|
if (openBrace === -1) return null;
|
|
const closeBrace = part.indexOf("}", openBrace);
|
|
if (closeBrace === -1) return null;
|
|
if (openBrace + 1 >= part.length) return null;
|
|
return [openBrace, closeBrace];
|
|
}
|
|
/**
|
|
* Populates the `output` array with the parsed representation of the given `segment` string.
|
|
*
|
|
* Usage:
|
|
* ```ts
|
|
* let output
|
|
* let cursor = 0
|
|
* while (cursor < path.length) {
|
|
* output = parseSegment(path, cursor, output)
|
|
* const end = output[5]
|
|
* cursor = end + 1
|
|
* ```
|
|
*
|
|
* `output` is stored outside to avoid allocations during repeated calls. It doesn't need to be typed
|
|
* or initialized, it will be done automatically.
|
|
*/
|
|
function parseSegment(path, start, output = new Uint16Array(6)) {
|
|
const next = path.indexOf("/", start);
|
|
const end = next === -1 ? path.length : next;
|
|
const part = path.substring(start, end);
|
|
if (!part || !part.includes("$")) {
|
|
output[0] = 0;
|
|
output[1] = start;
|
|
output[2] = start;
|
|
output[3] = end;
|
|
output[4] = end;
|
|
output[5] = end;
|
|
return output;
|
|
}
|
|
if (part === "$") {
|
|
const total = path.length;
|
|
output[0] = 2;
|
|
output[1] = start;
|
|
output[2] = start;
|
|
output[3] = total;
|
|
output[4] = total;
|
|
output[5] = total;
|
|
return output;
|
|
}
|
|
if (part.charCodeAt(0) === 36) {
|
|
output[0] = 1;
|
|
output[1] = start;
|
|
output[2] = start + 1;
|
|
output[3] = end;
|
|
output[4] = end;
|
|
output[5] = end;
|
|
return output;
|
|
}
|
|
const braces = getOpenAndCloseBraces(part);
|
|
if (braces) {
|
|
const [openBrace, closeBrace] = braces;
|
|
const firstChar = part.charCodeAt(openBrace + 1);
|
|
if (firstChar === 45) {
|
|
if (openBrace + 2 < part.length && part.charCodeAt(openBrace + 2) === 36) {
|
|
const paramStart = openBrace + 3;
|
|
const paramEnd = closeBrace;
|
|
if (paramStart < paramEnd) {
|
|
output[0] = 3;
|
|
output[1] = start + openBrace;
|
|
output[2] = start + paramStart;
|
|
output[3] = start + paramEnd;
|
|
output[4] = start + closeBrace + 1;
|
|
output[5] = end;
|
|
return output;
|
|
}
|
|
}
|
|
} else if (firstChar === 36) {
|
|
const dollarPos = openBrace + 1;
|
|
const afterDollar = openBrace + 2;
|
|
if (afterDollar === closeBrace) {
|
|
output[0] = 2;
|
|
output[1] = start + openBrace;
|
|
output[2] = start + dollarPos;
|
|
output[3] = start + afterDollar;
|
|
output[4] = start + closeBrace + 1;
|
|
output[5] = path.length;
|
|
return output;
|
|
}
|
|
output[0] = 1;
|
|
output[1] = start + openBrace;
|
|
output[2] = start + afterDollar;
|
|
output[3] = start + closeBrace;
|
|
output[4] = start + closeBrace + 1;
|
|
output[5] = end;
|
|
return output;
|
|
}
|
|
}
|
|
output[0] = 0;
|
|
output[1] = start;
|
|
output[2] = start;
|
|
output[3] = end;
|
|
output[4] = end;
|
|
output[5] = end;
|
|
return output;
|
|
}
|
|
/**
|
|
* Recursively parses the segments of the given route tree and populates a segment trie.
|
|
*
|
|
* @param data A reusable Uint16Array for parsing segments. (non important, we're just avoiding allocations)
|
|
* @param route The current route to parse.
|
|
* @param start The starting index for parsing within the route's full path.
|
|
* @param node The current segment node in the trie to populate.
|
|
* @param onRoute Callback invoked for each route processed.
|
|
*/
|
|
function parseSegments(defaultCaseSensitive, data, route, start, node, depth, onRoute) {
|
|
onRoute?.(route);
|
|
let cursor = start;
|
|
{
|
|
const path = route.fullPath ?? route.from;
|
|
const length = path.length;
|
|
const caseSensitive = route.options?.caseSensitive ?? defaultCaseSensitive;
|
|
const parseParams = route.options?.params?.parse ?? route.options?.parseParams;
|
|
while (cursor < length) {
|
|
const segment = parseSegment(path, cursor, data);
|
|
let nextNode;
|
|
const start = cursor;
|
|
const end = segment[5];
|
|
cursor = end + 1;
|
|
depth++;
|
|
switch (segment[0]) {
|
|
case 0: {
|
|
const value = path.substring(segment[2], segment[3]);
|
|
if (caseSensitive) {
|
|
const existingNode = node.static?.get(value);
|
|
if (existingNode) nextNode = existingNode;
|
|
else {
|
|
node.static ??= /* @__PURE__ */ new Map();
|
|
const next = createStaticNode(route.fullPath ?? route.from);
|
|
next.parent = node;
|
|
next.depth = depth;
|
|
nextNode = next;
|
|
node.static.set(value, next);
|
|
}
|
|
} else {
|
|
const name = value.toLowerCase();
|
|
const existingNode = node.staticInsensitive?.get(name);
|
|
if (existingNode) nextNode = existingNode;
|
|
else {
|
|
node.staticInsensitive ??= /* @__PURE__ */ new Map();
|
|
const next = createStaticNode(route.fullPath ?? route.from);
|
|
next.parent = node;
|
|
next.depth = depth;
|
|
nextNode = next;
|
|
node.staticInsensitive.set(name, next);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
const prefix_raw = path.substring(start, segment[1]);
|
|
const suffix_raw = path.substring(segment[4], end);
|
|
const actuallyCaseSensitive = caseSensitive && !!(prefix_raw || suffix_raw);
|
|
const prefix = !prefix_raw ? void 0 : actuallyCaseSensitive ? prefix_raw : prefix_raw.toLowerCase();
|
|
const suffix = !suffix_raw ? void 0 : actuallyCaseSensitive ? suffix_raw : suffix_raw.toLowerCase();
|
|
const existingNode = !parseParams && node.dynamic?.find((s) => !s.parse && s.caseSensitive === actuallyCaseSensitive && s.prefix === prefix && s.suffix === suffix);
|
|
if (existingNode) nextNode = existingNode;
|
|
else {
|
|
const next = createDynamicNode(1, route.fullPath ?? route.from, actuallyCaseSensitive, prefix, suffix);
|
|
nextNode = next;
|
|
next.depth = depth;
|
|
next.parent = node;
|
|
node.dynamic ??= [];
|
|
node.dynamic.push(next);
|
|
}
|
|
break;
|
|
}
|
|
case 3: {
|
|
const prefix_raw = path.substring(start, segment[1]);
|
|
const suffix_raw = path.substring(segment[4], end);
|
|
const actuallyCaseSensitive = caseSensitive && !!(prefix_raw || suffix_raw);
|
|
const prefix = !prefix_raw ? void 0 : actuallyCaseSensitive ? prefix_raw : prefix_raw.toLowerCase();
|
|
const suffix = !suffix_raw ? void 0 : actuallyCaseSensitive ? suffix_raw : suffix_raw.toLowerCase();
|
|
const existingNode = !parseParams && node.optional?.find((s) => !s.parse && s.caseSensitive === actuallyCaseSensitive && s.prefix === prefix && s.suffix === suffix);
|
|
if (existingNode) nextNode = existingNode;
|
|
else {
|
|
const next = createDynamicNode(3, route.fullPath ?? route.from, actuallyCaseSensitive, prefix, suffix);
|
|
nextNode = next;
|
|
next.parent = node;
|
|
next.depth = depth;
|
|
node.optional ??= [];
|
|
node.optional.push(next);
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
const prefix_raw = path.substring(start, segment[1]);
|
|
const suffix_raw = path.substring(segment[4], end);
|
|
const actuallyCaseSensitive = caseSensitive && !!(prefix_raw || suffix_raw);
|
|
const prefix = !prefix_raw ? void 0 : actuallyCaseSensitive ? prefix_raw : prefix_raw.toLowerCase();
|
|
const suffix = !suffix_raw ? void 0 : actuallyCaseSensitive ? suffix_raw : suffix_raw.toLowerCase();
|
|
const next = createDynamicNode(2, route.fullPath ?? route.from, actuallyCaseSensitive, prefix, suffix);
|
|
nextNode = next;
|
|
next.parent = node;
|
|
next.depth = depth;
|
|
node.wildcard ??= [];
|
|
node.wildcard.push(next);
|
|
}
|
|
}
|
|
node = nextNode;
|
|
}
|
|
if (parseParams && route.children && !route.isRoot && route.id && route.id.charCodeAt(route.id.lastIndexOf("/") + 1) === 95) {
|
|
const pathlessNode = createStaticNode(route.fullPath ?? route.from);
|
|
pathlessNode.kind = SEGMENT_TYPE_PATHLESS;
|
|
pathlessNode.parent = node;
|
|
depth++;
|
|
pathlessNode.depth = depth;
|
|
node.pathless ??= [];
|
|
node.pathless.push(pathlessNode);
|
|
node = pathlessNode;
|
|
}
|
|
const isLeaf = (route.path || !route.children) && !route.isRoot;
|
|
if (isLeaf && path.endsWith("/")) {
|
|
const indexNode = createStaticNode(route.fullPath ?? route.from);
|
|
indexNode.kind = SEGMENT_TYPE_INDEX;
|
|
indexNode.parent = node;
|
|
depth++;
|
|
indexNode.depth = depth;
|
|
node.index = indexNode;
|
|
node = indexNode;
|
|
}
|
|
node.parse = parseParams ?? null;
|
|
if (isLeaf && !node.route) {
|
|
node.route = route;
|
|
node.fullPath = route.fullPath ?? route.from;
|
|
}
|
|
}
|
|
if (route.children) for (const child of route.children) parseSegments(defaultCaseSensitive, data, child, cursor, node, depth, onRoute);
|
|
}
|
|
function sortDynamic(a, b) {
|
|
if (a.parse && !b.parse) return -1;
|
|
if (!a.parse && b.parse) return 1;
|
|
if (a.prefix && b.prefix && a.prefix !== b.prefix) {
|
|
if (a.prefix.startsWith(b.prefix)) return -1;
|
|
if (b.prefix.startsWith(a.prefix)) return 1;
|
|
}
|
|
if (a.suffix && b.suffix && a.suffix !== b.suffix) {
|
|
if (a.suffix.endsWith(b.suffix)) return -1;
|
|
if (b.suffix.endsWith(a.suffix)) return 1;
|
|
}
|
|
if (a.prefix && !b.prefix) return -1;
|
|
if (!a.prefix && b.prefix) return 1;
|
|
if (a.suffix && !b.suffix) return -1;
|
|
if (!a.suffix && b.suffix) return 1;
|
|
if (a.caseSensitive && !b.caseSensitive) return -1;
|
|
if (!a.caseSensitive && b.caseSensitive) return 1;
|
|
return 0;
|
|
}
|
|
function sortTreeNodes(node) {
|
|
if (node.pathless) for (const child of node.pathless) sortTreeNodes(child);
|
|
if (node.static) for (const child of node.static.values()) sortTreeNodes(child);
|
|
if (node.staticInsensitive) for (const child of node.staticInsensitive.values()) sortTreeNodes(child);
|
|
if (node.dynamic?.length) {
|
|
node.dynamic.sort(sortDynamic);
|
|
for (const child of node.dynamic) sortTreeNodes(child);
|
|
}
|
|
if (node.optional?.length) {
|
|
node.optional.sort(sortDynamic);
|
|
for (const child of node.optional) sortTreeNodes(child);
|
|
}
|
|
if (node.wildcard?.length) {
|
|
node.wildcard.sort(sortDynamic);
|
|
for (const child of node.wildcard) sortTreeNodes(child);
|
|
}
|
|
}
|
|
function createStaticNode(fullPath) {
|
|
return {
|
|
kind: 0,
|
|
depth: 0,
|
|
pathless: null,
|
|
index: null,
|
|
static: null,
|
|
staticInsensitive: null,
|
|
dynamic: null,
|
|
optional: null,
|
|
wildcard: null,
|
|
route: null,
|
|
fullPath,
|
|
parent: null,
|
|
parse: null
|
|
};
|
|
}
|
|
/**
|
|
* Keys must be declared in the same order as in `SegmentNode` type,
|
|
* to ensure they are represented as the same object class in the engine.
|
|
*/
|
|
function createDynamicNode(kind, fullPath, caseSensitive, prefix, suffix) {
|
|
return {
|
|
kind,
|
|
depth: 0,
|
|
pathless: null,
|
|
index: null,
|
|
static: null,
|
|
staticInsensitive: null,
|
|
dynamic: null,
|
|
optional: null,
|
|
wildcard: null,
|
|
route: null,
|
|
fullPath,
|
|
parent: null,
|
|
parse: null,
|
|
caseSensitive,
|
|
prefix,
|
|
suffix
|
|
};
|
|
}
|
|
function processRouteMasks(routeList, processedTree) {
|
|
const segmentTree = createStaticNode("/");
|
|
const data = new Uint16Array(6);
|
|
for (const route of routeList) parseSegments(false, data, route, 1, segmentTree, 0);
|
|
sortTreeNodes(segmentTree);
|
|
processedTree.masksTree = segmentTree;
|
|
processedTree.flatCache = createLRUCache(1e3);
|
|
}
|
|
/**
|
|
* Take an arbitrary list of routes, create a tree from them (if it hasn't been created already), and match a path against it.
|
|
*/
|
|
function findFlatMatch(path, processedTree) {
|
|
path ||= "/";
|
|
const cached = processedTree.flatCache.get(path);
|
|
if (cached) return cached;
|
|
const result = findMatch(path, processedTree.masksTree);
|
|
processedTree.flatCache.set(path, result);
|
|
return result;
|
|
}
|
|
/**
|
|
* @deprecated keep until v2 so that `router.matchRoute` can keep not caring about the actual route tree
|
|
*/
|
|
function findSingleMatch(from, caseSensitive, fuzzy, path, processedTree) {
|
|
from ||= "/";
|
|
path ||= "/";
|
|
const key = caseSensitive ? `case\0${from}` : from;
|
|
let tree = processedTree.singleCache.get(key);
|
|
if (!tree) {
|
|
tree = createStaticNode("/");
|
|
parseSegments(caseSensitive, new Uint16Array(6), { from }, 1, tree, 0);
|
|
processedTree.singleCache.set(key, tree);
|
|
}
|
|
return findMatch(path, tree, fuzzy);
|
|
}
|
|
function findRouteMatch(path, processedTree, fuzzy = false) {
|
|
const key = fuzzy ? path : `nofuzz\0${path}`;
|
|
const cached = processedTree.matchCache.get(key);
|
|
if (cached !== void 0) return cached;
|
|
path ||= "/";
|
|
let result;
|
|
try {
|
|
result = findMatch(path, processedTree.segmentTree, fuzzy);
|
|
} catch (err) {
|
|
if (err instanceof URIError) result = null;
|
|
else throw err;
|
|
}
|
|
if (result) result.branch = buildRouteBranch(result.route);
|
|
processedTree.matchCache.set(key, result);
|
|
return result;
|
|
}
|
|
/** Trim trailing slashes (except preserving root '/'). */
|
|
function trimPathRight(path) {
|
|
return path === "/" ? path : path.replace(/\/{1,}$/, "");
|
|
}
|
|
/**
|
|
* Processes a route tree into a segment trie for efficient path matching.
|
|
* Also builds lookup maps for routes by ID and by trimmed full path.
|
|
*/
|
|
function processRouteTree(routeTree, caseSensitive = false, initRoute) {
|
|
const segmentTree = createStaticNode(routeTree.fullPath);
|
|
const data = new Uint16Array(6);
|
|
const routesById = {};
|
|
const routesByPath = {};
|
|
let index = 0;
|
|
parseSegments(caseSensitive, data, routeTree, 1, segmentTree, 0, (route) => {
|
|
initRoute?.(route, index);
|
|
if (route.id in routesById) {
|
|
if (process.env.NODE_ENV !== "production") throw new Error(`Invariant failed: Duplicate routes found with id: ${String(route.id)}`);
|
|
invariant();
|
|
}
|
|
routesById[route.id] = route;
|
|
if (index !== 0 && route.path) {
|
|
const trimmedFullPath = trimPathRight(route.fullPath);
|
|
if (!routesByPath[trimmedFullPath] || route.fullPath.endsWith("/")) routesByPath[trimmedFullPath] = route;
|
|
}
|
|
index++;
|
|
});
|
|
sortTreeNodes(segmentTree);
|
|
return {
|
|
processedTree: {
|
|
segmentTree,
|
|
singleCache: createLRUCache(1e3),
|
|
matchCache: createLRUCache(1e3),
|
|
flatCache: null,
|
|
masksTree: null
|
|
},
|
|
routesById,
|
|
routesByPath
|
|
};
|
|
}
|
|
function findMatch(path, segmentTree, fuzzy = false) {
|
|
const parts = path.split("/");
|
|
const leaf = getNodeMatch(path, parts, segmentTree, fuzzy);
|
|
if (!leaf) return null;
|
|
const [rawParams] = extractParams(path, parts, leaf);
|
|
return {
|
|
route: leaf.node.route,
|
|
rawParams
|
|
};
|
|
}
|
|
/**
|
|
* This function is "resumable":
|
|
* - the `leaf` input can contain `extract` and `rawParams` properties from a previous `extractParams` call
|
|
* - the returned `state` can be passed back as `extract` in a future call to continue extracting params from where we left off
|
|
*
|
|
* Inputs are *not* mutated.
|
|
*/
|
|
function extractParams(path, parts, leaf) {
|
|
const list = buildBranch(leaf.node);
|
|
let nodeParts = null;
|
|
const rawParams = Object.create(null);
|
|
/** which segment of the path we're currently processing */
|
|
let partIndex = leaf.extract?.part ?? 0;
|
|
/** which node of the route tree branch we're currently processing */
|
|
let nodeIndex = leaf.extract?.node ?? 0;
|
|
/** index of the 1st character of the segment we're processing in the path string */
|
|
let pathIndex = leaf.extract?.path ?? 0;
|
|
/** which fullPath segment we're currently processing */
|
|
let segmentCount = leaf.extract?.segment ?? 0;
|
|
for (; nodeIndex < list.length; partIndex++, nodeIndex++, pathIndex++, segmentCount++) {
|
|
const node = list[nodeIndex];
|
|
if (node.kind === SEGMENT_TYPE_INDEX) break;
|
|
if (node.kind === SEGMENT_TYPE_PATHLESS) {
|
|
segmentCount--;
|
|
partIndex--;
|
|
pathIndex--;
|
|
continue;
|
|
}
|
|
const part = parts[partIndex];
|
|
const currentPathIndex = pathIndex;
|
|
if (part) pathIndex += part.length;
|
|
if (node.kind === 1) {
|
|
nodeParts ??= leaf.node.fullPath.split("/");
|
|
const nodePart = nodeParts[segmentCount];
|
|
const preLength = node.prefix?.length ?? 0;
|
|
if (nodePart.charCodeAt(preLength) === 123) {
|
|
const sufLength = node.suffix?.length ?? 0;
|
|
const name = nodePart.substring(preLength + 2, nodePart.length - sufLength - 1);
|
|
const value = part.substring(preLength, part.length - sufLength);
|
|
rawParams[name] = decodeURIComponent(value);
|
|
} else {
|
|
const name = nodePart.substring(1);
|
|
rawParams[name] = decodeURIComponent(part);
|
|
}
|
|
} else if (node.kind === 3) {
|
|
if (leaf.skipped & 1 << nodeIndex) {
|
|
partIndex--;
|
|
pathIndex = currentPathIndex - 1;
|
|
continue;
|
|
}
|
|
nodeParts ??= leaf.node.fullPath.split("/");
|
|
const nodePart = nodeParts[segmentCount];
|
|
const preLength = node.prefix?.length ?? 0;
|
|
const sufLength = node.suffix?.length ?? 0;
|
|
const name = nodePart.substring(preLength + 3, nodePart.length - sufLength - 1);
|
|
const value = node.suffix || node.prefix ? part.substring(preLength, part.length - sufLength) : part;
|
|
if (value) rawParams[name] = decodeURIComponent(value);
|
|
} else if (node.kind === 2) {
|
|
const n = node;
|
|
const value = path.substring(currentPathIndex + (n.prefix?.length ?? 0), path.length - (n.suffix?.length ?? 0));
|
|
const splat = decodeURIComponent(value);
|
|
rawParams["*"] = splat;
|
|
rawParams._splat = splat;
|
|
break;
|
|
}
|
|
}
|
|
if (leaf.rawParams) Object.assign(rawParams, leaf.rawParams);
|
|
return [rawParams, {
|
|
part: partIndex,
|
|
node: nodeIndex,
|
|
path: pathIndex,
|
|
segment: segmentCount
|
|
}];
|
|
}
|
|
function buildRouteBranch(route) {
|
|
const list = [route];
|
|
while (route.parentRoute) {
|
|
route = route.parentRoute;
|
|
list.push(route);
|
|
}
|
|
list.reverse();
|
|
return list;
|
|
}
|
|
function buildBranch(node) {
|
|
const list = Array(node.depth + 1);
|
|
do {
|
|
list[node.depth] = node;
|
|
node = node.parent;
|
|
} while (node);
|
|
return list;
|
|
}
|
|
function getNodeMatch(path, parts, segmentTree, fuzzy) {
|
|
if (path === "/" && segmentTree.index) return {
|
|
node: segmentTree.index,
|
|
skipped: 0
|
|
};
|
|
const trailingSlash = !last(parts);
|
|
const pathIsIndex = trailingSlash && path !== "/";
|
|
const partsLength = parts.length - (trailingSlash ? 1 : 0);
|
|
const stack = [{
|
|
node: segmentTree,
|
|
index: 1,
|
|
skipped: 0,
|
|
depth: 1,
|
|
statics: 0,
|
|
dynamics: 0,
|
|
optionals: 0
|
|
}];
|
|
let bestFuzzy = null;
|
|
let bestMatch = null;
|
|
while (stack.length) {
|
|
const frame = stack.pop();
|
|
const { node, index, skipped, depth, statics, dynamics, optionals } = frame;
|
|
let { extract, rawParams } = frame;
|
|
if (node.kind === 2 && node.route && !isFrameMoreSpecific(bestMatch, frame)) continue;
|
|
if (node.parse) {
|
|
if (!validateParseParams(path, parts, frame)) continue;
|
|
rawParams = frame.rawParams;
|
|
extract = frame.extract;
|
|
}
|
|
if (fuzzy && node.route && node.kind !== SEGMENT_TYPE_INDEX && isFrameMoreSpecific(bestFuzzy, frame)) bestFuzzy = frame;
|
|
const isBeyondPath = index === partsLength;
|
|
if (isBeyondPath) {
|
|
if (node.route && (!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX || node.kind === 2) && isFrameMoreSpecific(bestMatch, frame)) bestMatch = frame;
|
|
if (!node.optional && !node.wildcard && !node.index && !node.pathless) continue;
|
|
}
|
|
const part = isBeyondPath ? void 0 : parts[index];
|
|
let lowerPart;
|
|
if (isBeyondPath && node.index) {
|
|
const indexFrame = {
|
|
node: node.index,
|
|
index,
|
|
skipped,
|
|
depth: depth + 1,
|
|
statics,
|
|
dynamics,
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
};
|
|
let indexValid = true;
|
|
if (node.index.parse) {
|
|
if (!validateParseParams(path, parts, indexFrame)) indexValid = false;
|
|
}
|
|
if (indexValid) {
|
|
if (!dynamics && !optionals && !skipped && isPerfectStaticMatch(statics, partsLength)) return indexFrame;
|
|
if (isFrameMoreSpecific(bestMatch, indexFrame)) bestMatch = indexFrame;
|
|
}
|
|
}
|
|
if (node.wildcard) for (let i = node.wildcard.length - 1; i >= 0; i--) {
|
|
const segment = node.wildcard[i];
|
|
const { prefix, suffix } = segment;
|
|
if (prefix) {
|
|
if (isBeyondPath) continue;
|
|
if (!(segment.caseSensitive ? part : lowerPart ??= part.toLowerCase()).startsWith(prefix)) continue;
|
|
}
|
|
if (suffix) {
|
|
if (isBeyondPath) continue;
|
|
const end = parts.slice(index).join("/").slice(-suffix.length);
|
|
if ((segment.caseSensitive ? end : end.toLowerCase()) !== suffix) continue;
|
|
}
|
|
stack.push({
|
|
node: segment,
|
|
index: partsLength,
|
|
skipped,
|
|
depth: depth + 1,
|
|
statics,
|
|
dynamics,
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
if (node.optional) {
|
|
const nextSkipped = skipped | 1 << depth;
|
|
const nextDepth = depth + 1;
|
|
for (let i = node.optional.length - 1; i >= 0; i--) {
|
|
const segment = node.optional[i];
|
|
stack.push({
|
|
node: segment,
|
|
index,
|
|
skipped: nextSkipped,
|
|
depth: nextDepth,
|
|
statics,
|
|
dynamics,
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
if (!isBeyondPath) for (let i = node.optional.length - 1; i >= 0; i--) {
|
|
const segment = node.optional[i];
|
|
const { prefix, suffix } = segment;
|
|
if (prefix || suffix) {
|
|
const casePart = segment.caseSensitive ? part : lowerPart ??= part.toLowerCase();
|
|
if (prefix && !casePart.startsWith(prefix)) continue;
|
|
if (suffix && !casePart.endsWith(suffix)) continue;
|
|
}
|
|
stack.push({
|
|
node: segment,
|
|
index: index + 1,
|
|
skipped,
|
|
depth: nextDepth,
|
|
statics,
|
|
dynamics,
|
|
optionals: optionals + segmentScore(partsLength, index),
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
}
|
|
if (!isBeyondPath && node.dynamic && part) for (let i = node.dynamic.length - 1; i >= 0; i--) {
|
|
const segment = node.dynamic[i];
|
|
const { prefix, suffix } = segment;
|
|
if (prefix || suffix) {
|
|
const casePart = segment.caseSensitive ? part : lowerPart ??= part.toLowerCase();
|
|
if (prefix && !casePart.startsWith(prefix)) continue;
|
|
if (suffix && !casePart.endsWith(suffix)) continue;
|
|
}
|
|
stack.push({
|
|
node: segment,
|
|
index: index + 1,
|
|
skipped,
|
|
depth: depth + 1,
|
|
statics,
|
|
dynamics: dynamics + segmentScore(partsLength, index),
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
if (!isBeyondPath && node.staticInsensitive) {
|
|
const match = node.staticInsensitive.get(lowerPart ??= part.toLowerCase());
|
|
if (match) stack.push({
|
|
node: match,
|
|
index: index + 1,
|
|
skipped,
|
|
depth: depth + 1,
|
|
statics: statics + segmentScore(partsLength, index),
|
|
dynamics,
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
if (!isBeyondPath && node.static) {
|
|
const match = node.static.get(part);
|
|
if (match) stack.push({
|
|
node: match,
|
|
index: index + 1,
|
|
skipped,
|
|
depth: depth + 1,
|
|
statics: statics + segmentScore(partsLength, index),
|
|
dynamics,
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
if (node.pathless) {
|
|
const nextDepth = depth + 1;
|
|
for (let i = node.pathless.length - 1; i >= 0; i--) {
|
|
const segment = node.pathless[i];
|
|
stack.push({
|
|
node: segment,
|
|
index,
|
|
skipped,
|
|
depth: nextDepth,
|
|
statics,
|
|
dynamics,
|
|
optionals,
|
|
extract,
|
|
rawParams
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (bestMatch) return bestMatch;
|
|
if (fuzzy && bestFuzzy) {
|
|
let sliceIndex = bestFuzzy.index;
|
|
for (let i = 0; i < bestFuzzy.index; i++) sliceIndex += parts[i].length;
|
|
const splat = sliceIndex === path.length ? "/" : path.slice(sliceIndex);
|
|
bestFuzzy.rawParams ??= Object.create(null);
|
|
bestFuzzy.rawParams["**"] = decodeURIComponent(splat);
|
|
return bestFuzzy;
|
|
}
|
|
return null;
|
|
}
|
|
function segmentScore(partsLength, index) {
|
|
return 2 ** (partsLength - index - 1);
|
|
}
|
|
function isPerfectStaticMatch(statics, partsLength) {
|
|
return statics === 2 ** (partsLength - 1) - 1;
|
|
}
|
|
function validateParseParams(path, parts, frame) {
|
|
let rawParams;
|
|
let state;
|
|
try {
|
|
[rawParams, state] = extractParams(path, parts, frame);
|
|
} catch {
|
|
return null;
|
|
}
|
|
frame.rawParams = rawParams;
|
|
frame.extract = state;
|
|
if (!frame.node.parse) return true;
|
|
try {
|
|
if (frame.node.parse(rawParams) === false) return null;
|
|
} catch {}
|
|
return true;
|
|
}
|
|
function isFrameMoreSpecific(prev, next) {
|
|
if (!prev) return true;
|
|
return next.statics > prev.statics || next.statics === prev.statics && (next.dynamics > prev.dynamics || next.dynamics === prev.dynamics && (next.optionals > prev.optionals || next.optionals === prev.optionals && ((next.node.kind === SEGMENT_TYPE_INDEX) > (prev.node.kind === SEGMENT_TYPE_INDEX) || next.node.kind === SEGMENT_TYPE_INDEX === (prev.node.kind === SEGMENT_TYPE_INDEX) && next.depth > prev.depth)));
|
|
}
|
|
//#endregion
|
|
export { buildRouteBranch, findFlatMatch, findRouteMatch, findSingleMatch, parseSegment, processRouteMasks, processRouteTree };
|
|
|
|
//# sourceMappingURL=new-process-route-tree.js.map |