Skip to main content

pattern

Glob matching, prefix/suffix/contains, and tuple filtering.

import type {
  MatchesGlob,
  StartsAnyOf, EndsAnyOf, IncludesAnyOf,
  FilterMatching,
} from '@gentleduck/ttest/pattern'

Glob semantics:

  • * — zero or more characters (greedy substring)
  • ? — exactly one character
  • ** — any depth, including across /

MatchesGlob

type MatchesGlob<S extends string, P extends string>

true if S matches glob P. ** triggers branchy backtracking — worst case is quadratic. Fine for tooling; avoid in hot generic constraints.

type a = MatchesGlob<'src/app.ts', 'src/*.ts'>           // true
type b = MatchesGlob<'src/x/y.ts', 'src/*.ts'>           // false
type c = MatchesGlob<'src/x/y.ts', 'src/**'>             // true
type d = MatchesGlob<'index.ts',   '?ndex.ts'>           // true

StartsAnyOf

type StartsAnyOf<S extends string, Prefixes extends readonly string[]>

true if S starts with any of Prefixes.

type a = StartsAnyOf<'/api/users', ['/api', '/auth']>  // true

EndsAnyOf

type EndsAnyOf<S extends string, Suffixes extends readonly string[]>
type a = EndsAnyOf<'logo.svg', ['.png', '.svg']>  // true

IncludesAnyOf

type IncludesAnyOf<S extends string, Needles extends readonly string[]>

true if S contains any of the substrings.

type a = IncludesAnyOf<'hello world', ['foo', 'world']>  // true

FilterMatching

type FilterMatching<T extends readonly string[], P extends string, Acc extends string[] = []>

Keep only the strings in T that match glob P.

type t = FilterMatching<['a.ts', 'b.js', 'c.ts'], '*.ts'>  // ['a.ts', 'c.ts']