Skip to content

Commit

Permalink
refactor(host-rules): Simplify ordering and matching (#28512)
Browse files Browse the repository at this point in the history
Co-authored-by: Rhys Arkins <rhys@arkins.net>
  • Loading branch information
zharinov and rarkins authored Apr 21, 2024
1 parent ec2e5d7 commit 49afe4e
Showing 1 changed file with 56 additions and 75 deletions.
131 changes: 56 additions & 75 deletions lib/util/host-rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import is from '@sindresorhus/is';
import merge from 'deepmerge';
import type { SetRequired } from 'type-fest';
import { logger } from '../logger';
import type { CombinedHostRule, HostRule } from '../types';
import { clone } from './clone';
Expand Down Expand Up @@ -77,36 +75,6 @@ export interface HostRuleSearch {
url?: string;
}

function isEmpty(
rule: HostRule,
): rule is Omit<HostRule, 'hostType' | 'matchHost' | 'resolvedHost'> {
return !rule.hostType && !rule.resolvedHost;
}

function isComplete(
rule: HostRule,
): rule is SetRequired<HostRule, 'hostType' | 'matchHost' | 'resolvedHost'> {
return !!rule.hostType && !!rule.resolvedHost;
}

function isOnlyHostType(
rule: HostRule,
): rule is Omit<
SetRequired<HostRule, 'hostType'>,
'matchHost' | 'resolvedHost'
> {
return !!rule.hostType && !rule.resolvedHost;
}

function isOnlyMatchHost(
rule: HostRule,
): rule is Omit<
SetRequired<HostRule, 'matchHost' | 'resolvedHost'>,
'hostType'
> {
return !rule.hostType && !!rule.matchHost;
}

function matchesHost(url: string, matchHost: string): boolean {
if (isHttpUrl(url) && isHttpUrl(matchHost)) {
return url.startsWith(matchHost);
Expand All @@ -132,56 +100,69 @@ function matchesHost(url: string, matchHost: string): boolean {
return hostname.endsWith(topLevelSuffix);
}

function prioritizeLongestMatchHost(rule1: HostRule, rule2: HostRule): number {
// istanbul ignore if: won't happen in practice
if (!rule1.matchHost || !rule2.matchHost) {
function fromShorterToLongerMatchHost(a: HostRule, b: HostRule): number {
if (!a.matchHost || !b.matchHost) {
return 0;
}
return rule1.matchHost.length - rule2.matchHost.length;
return a.matchHost.length - b.matchHost.length;
}

function hostRuleRank({ hostType, matchHost }: HostRule): number {
if (hostType && matchHost) {
return 3;
}

if (matchHost) {
return 2;
}

if (hostType) {
return 1;
}

return 0;
}

function fromLowerToHigherRank(a: HostRule, b: HostRule): number {
return hostRuleRank(a) - hostRuleRank(b);
}

export function find(search: HostRuleSearch): CombinedHostRule {
if (!(!!search.hostType || search.url)) {
if ([search.hostType, search.url].every(is.falsy)) {
logger.warn({ search }, 'Invalid hostRules search');
return {};
}
let res: HostRule = {};
// First, apply empty rule matches
hostRules
.filter((rule) => isEmpty(rule))
.forEach((rule) => {
res = merge(res, rule);
});
// Next, find hostType-only matches
hostRules
.filter((rule) => isOnlyHostType(rule) && rule.hostType === search.hostType)
.forEach((rule) => {
res = merge(res, rule);
});
hostRules
.filter(
(rule) =>
isOnlyMatchHost(rule) &&
search.url &&
matchesHost(search.url, rule.matchHost),
)
.sort(prioritizeLongestMatchHost)
.forEach((rule) => {
res = merge(res, rule);
});
// Finally, find combination matches
hostRules
.filter(
(rule) =>
isComplete(rule) &&
rule.hostType === search.hostType &&
search.url &&
matchesHost(search.url, rule.matchHost),
)
.sort(prioritizeLongestMatchHost)
.forEach((rule) => {
res = merge(res, rule);
});

// Sort primarily by rank, and secondarily by matchHost length
const sortedRules = hostRules
.sort(fromShorterToLongerMatchHost)
.sort(fromLowerToHigherRank);

const matchedRules: HostRule[] = [];
for (const rule of sortedRules) {
let hostTypeMatch = true;
let hostMatch = true;

if (rule.hostType) {
hostTypeMatch = false;
if (search.hostType === rule.hostType) {
hostTypeMatch = true;
}
}

if (rule.matchHost && rule.resolvedHost) {
hostMatch = false;
if (search.url) {
hostMatch = matchesHost(search.url, rule.matchHost);
}
}

if (hostTypeMatch && hostMatch) {
matchedRules.push(clone(rule));
}
}

const res: HostRule = Object.assign({}, ...matchedRules);
delete res.hostType;
delete res.resolvedHost;
delete res.matchHost;
Expand All @@ -199,7 +180,7 @@ export function hostType({ url }: { url: string }): string | null {
return (
hostRules
.filter((rule) => rule.matchHost && matchesHost(url, rule.matchHost))
.sort(prioritizeLongestMatchHost)
.sort(fromShorterToLongerMatchHost)
.map((rule) => rule.hostType)
.filter(is.truthy)
.pop() ?? null
Expand Down

0 comments on commit 49afe4e

Please sign in to comment.