From 4dc54322cbe72bec3fa644fdb21a1049fb49758c Mon Sep 17 00:00:00 2001 From: alyldas Date: Wed, 1 Jul 2026 17:18:00 +0300 Subject: [PATCH] Align email scanner contract --- src/filter.ts | 17 ++--------------- src/index.ts | 1 + src/types.ts | 22 ++++++++++++---------- tests/scanner.spec.ts | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/filter.ts b/src/filter.ts index 2570715..157d242 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -1,8 +1,4 @@ -import { - maskCodePointRangesPreservingLength, - normalizeTextInput, - type TextCodePointRange, -} from "@textfilters/core"; +import { censorCodePointRanges, normalizeTextInput } from "@textfilters/core"; import { createEmailScanner } from "./scanner.js"; import { @@ -11,15 +7,6 @@ import { type EmailFilterOptions, } from "./types.js"; -const maskEmailRanges = ( - codePoints: readonly string[], - ranges: readonly TextCodePointRange[], - maskChar: string, -): string => { - if (ranges.length === 0) return codePoints.join(""); - return maskCodePointRangesPreservingLength(codePoints, ranges, maskChar); -}; - export function createEmailFilter( options: EmailFilterOptions = {}, ): EmailFilter { @@ -33,7 +20,7 @@ export function createEmailFilter( if (!source) return source; const codePoints = Array.from(source); const ranges = scanner.scan({ text: source, codePoints }).ranges; - return maskEmailRanges(codePoints, ranges, maskChar); + return censorCodePointRanges(codePoints, ranges, maskChar); }, }; } diff --git a/src/index.ts b/src/index.ts index 4a7412e..36dd4b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ export { type EmailRangeScanResult, type EmailFilter, type EmailFilterOptions, + type EmailScanHints, type EmailScanInput, } from "./types.js"; export { diff --git a/src/types.ts b/src/types.ts index b995889..54c3fa1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,24 +17,26 @@ export interface EmailFilter { censor(text: unknown): string; } +export interface EmailScanHints { + readonly textLength?: number; + readonly hasNonAscii?: boolean; + readonly hasAtSign?: boolean; + readonly hasDot?: boolean; +} + export interface EmailScanInput { readonly text: string; readonly codePoints: readonly string[]; - readonly hints?: { - readonly textLength?: number; - readonly hasNonAscii?: boolean; - readonly hasAtSign?: boolean; - readonly hasDot?: boolean; - }; + readonly hints?: EmailScanHints; } -export interface EmailRangeScanResult { +export type EmailRangeScanResult = { readonly ranges: readonly TextCodePointRange[]; -} +}; -export interface EmailRangeMatch { +export type EmailRangeMatch = { readonly range: TextCodePointRange; -} +}; export type EmailRangeMatchSink = (match: EmailRangeMatch) => boolean | void; diff --git a/tests/scanner.spec.ts b/tests/scanner.spec.ts index d586dea..37ae0f8 100644 --- a/tests/scanner.spec.ts +++ b/tests/scanner.spec.ts @@ -2,13 +2,38 @@ import { describe, expect, it } from "vitest"; import { checkEmailRanges, + createEmailFilter, createEmailScanner, scanEmailRangeMatches, scanEmailRanges, EMAIL_FILTER_NAME, + type EmailRangeScanner, + type EmailRangeScanResult, + type EmailScanHints, } from "../src/index.js"; +const mask = (value: string, maskChar = "*"): string => + maskChar.repeat(Array.from(value).length); + describe("@textfilters/email scanner", () => { + it("keeps scanner contracts compatible with shared range shapes", () => { + const scanner: EmailRangeScanner = createEmailScanner(); + const hints: EmailScanHints = { + textLength: "contact user@example.com now".length, + hasNonAscii: false, + hasAtSign: true, + hasDot: true, + }; + const text = "contact user@example.com now"; + const result: EmailRangeScanResult = scanner.scan({ + text, + codePoints: Array.from(text), + hints, + }); + + expect(result).toEqual({ ranges: [[8, 24]] }); + }); + it("exposes scanner ranges compatible with code point masking", () => { const scanner = createEmailScanner(); expect( @@ -22,6 +47,20 @@ describe("@textfilters/email scanner", () => { expect(scanner.name).toBe(EMAIL_FILTER_NAME); }); + it("keeps the public censor wrapper aligned with scanner ranges", () => { + const text = "contact user@example.com now"; + const scanner = createEmailScanner(); + const ranges = scanner.scan({ + text, + codePoints: Array.from(text), + }).ranges; + + expect(ranges).toEqual([[8, 24]]); + expect(createEmailFilter({ maskChar: "#" }).censor(text)).toBe( + `contact ${mask("user@example.com", "#")} now`, + ); + }); + it("keeps direct and obfuscated coverage through the scanner path", () => { expect(scanEmailRanges("contact user@example.com")).toEqual([[8, 24]]); expect(scanEmailRanges("contact user [at] example [dot] com")).toEqual([