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
22 changes: 12 additions & 10 deletions src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,26 @@ export interface UrlFilter {
censor(text: unknown): string;
}

export interface UrlScanHints {
readonly hasNonAscii?: boolean;
readonly hasDot?: boolean;
readonly hasSlash?: boolean;
readonly hasColon?: boolean;
}

export interface UrlScanInput {
readonly text: string;
readonly codePoints: readonly string[];
readonly hints?: {
readonly hasNonAscii?: boolean;
readonly hasDot?: boolean;
readonly hasSlash?: boolean;
readonly hasColon?: boolean;
};
readonly hints?: UrlScanHints;
}

export interface UrlRangeScanResult {
export type UrlRangeScanResult = {
readonly ranges: readonly TextCodePointRange[];
}
};

export interface UrlRangeMatch {
export type UrlRangeMatch = {
readonly range: TextCodePointRange;
}
};

export type UrlRangeMatchSink = (match: UrlRangeMatch) => boolean | void;

Expand Down
18 changes: 3 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
maskCodePointRangesPreservingLength,
normalizeTextInput,
type TextCodePointRange,
} from "@textfilters/core";
import { censorCodePointRanges, normalizeTextInput } from "@textfilters/core";

import {
URL_FILTER_NAME,
Expand All @@ -20,6 +16,7 @@ export {
type UrlRangeMatchSink,
type UrlRangeScanner,
type UrlRangeScanResult,
type UrlScanHints,
type UrlScanInput,
} from "./contracts.js";
export {
Expand All @@ -30,15 +27,6 @@ export {
type UrlScannerConfig,
} from "./scanner.js";

const maskUrlRanges = (
codePoints: readonly string[],
ranges: readonly TextCodePointRange[],
maskChar: string,
): string => {
if (ranges.length === 0) return codePoints.join("");
return maskCodePointRangesPreservingLength(codePoints, ranges, maskChar);
};

export function createUrlFilter(config: UrlFilterConfig = {}): UrlFilter {
const scanner = createUrlScanner({ tlds: normalizeTlds(config.tlds) });
const maskChar = config.maskChar ?? "*";
Expand All @@ -50,7 +38,7 @@ export function createUrlFilter(config: UrlFilterConfig = {}): UrlFilter {
if (!source) return source;
const codePoints = Array.from(source);
const ranges = scanner.scan({ text: source, codePoints }).ranges;
return maskUrlRanges(codePoints, ranges, maskChar);
return censorCodePointRanges(codePoints, ranges, maskChar);
},
};
}
Expand Down
35 changes: 35 additions & 0 deletions tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
scanUrlRangeMatches,
scanUrlRanges,
URL_FILTER_NAME,
type UrlRangeScanner,
type UrlRangeScanResult,
type UrlScanHints,
urlFilter,
} from "../src/index.js";

Expand Down Expand Up @@ -334,6 +337,24 @@ describe("compatibility behavior", () => {
});

describe("URL scanner", () => {
it("keeps scanner contracts compatible with shared range shapes", () => {
const scanner: UrlRangeScanner = createUrlScanner();
const hints: UrlScanHints = {
hasNonAscii: false,
hasDot: true,
hasSlash: false,
hasColon: false,
};
const text = "visit example.com now";
const result: UrlRangeScanResult = scanner.scan({
text,
codePoints: Array.from(text),
hints,
});

expect(result).toEqual({ ranges: [[6, 17]] });
});

it("exposes scanner ranges compatible with code point masking", () => {
const scanner = createUrlScanner();
expect(
Expand All @@ -346,6 +367,20 @@ describe("URL scanner", () => {
});
});

it("keeps the public censor wrapper aligned with scanner ranges", () => {
const text = "go https://example.com/path now";
const scanner = createUrlScanner();
const ranges = scanner.scan({
text,
codePoints: Array.from(text),
}).ranges;

expect(ranges).toEqual([[3, 27]]);
expect(createUrlFilter({ maskChar: "#" }).censor(text)).toBe(
`go ${mask("https://example.com/path", "#")} now`,
);
});

it("checks URL candidates without collecting every range", () => {
const scanner = createUrlScanner();
const text = "visit https://example.com and https://second.example now";
Expand Down