shau-kote
shau-kote

Reputation: 1150

Is it possible to describe recursive string format in TS?

I want to describe strings a certain format using TS. Just like it сan be done through regular expressions or Backus–Naur form, but through template literal types.

I wrote this:


type LowercaseLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
type UppercaseLetter = Uppercase<LowercaseLetter>;
type Letter = LowercaseLetter | UppercaseLetter;

type Segment = `${Letter}${Segment | ''}`
type Path = `${Segment}${`/${Segment}` | ''}`

But I get an error: Type alias 'Segment' circularly references itself. (2456).

Is there some way to bypass this restriction?

Thank you.

Upvotes: 0

Views: 45

Answers (1)

Dimava
Dimava

Reputation: 10919

You can't define a such type, but you can check is existing type follows theese limitations

type TrimLeft<S extends string, C extends string> =
  | string extends S ? S
  : S extends `${C}${infer A extends string}` ? TrimLeft<A, C>
  : S;


type str = 'iayscbicb823'

type t1 = TrimLeft<string, Letter>

type ConsistsOf<S extends string, C extends string> = TrimLeft<S, C> extends "" ? true : false;


type test1 = ConsistsOf<"akjbcajkd", Letter>

function isWord<T extends string, L = TrimLeft<T, Letter> >(
  v: T & (L extends "" ? T : never)
): L extends "" ? true : false {
  return !!v.match(/^[a-z]$/i) as any
}

isWord('asd') // `true`
isWord('asd6') // invalid argument, and returns `false` (choose one)

For a usable type, make a typecheck function which returns a branded type

declare const Brand: unique symbol; // or just as '_' string 
type Branded<T, B> = T & {[Brand]: B}

function isWord2<T extends string, L = TrimLeft<T, Letter> >(v: T): v is Branded<T, 'word' & {chars: Letter}> {
  return !!v.match(/^[a-z]$/i) as any
}

let s = 'dfs';
if (isWord2(s)) {
  s
}

Upvotes: 2

Related Questions