Scenario Background:
// Code snippet to do validation - not the main focus.
type Validate<N, S> = [S] extends [N] ? N : never;
// Note that by uncommenting below line, a circular constraint will be introduced when used in validateName().
// type Validate<N, S> = S extends N ? N : never;
// Validation function - implementation as JS (or return value) is not crucial.
// .. But it's important to use a Validate type with two arguments as shown above.
function validateName<N extends string, S extends Validate<N, S>>(input: S) {}
Issue:
How can we provide only N
without specifying S
to the validateName
(or Validate
) mentioned above? The goal is to let S
be inferred from the actual argument.
// Test.
type ValidNames = "bold" | "italic";
// Desired usage:
// .. But this is not possible due to "Expected 2 type arguments, but got 1."
validateName<ValidNames>("bold"); // Ok.
validateName<ValidNames>("bald"); // Error.
// Issue unresolved due to: "Type parameter defaults can only reference previously declared type parameters."
function validateName<N extends string, S extends Validate<N, S> = Validate<N, S>>(input: S) {}
Possible Solutions:
Solution #1: Assign input to a variable and use its type.
const input1 = "bold";
const input2 = "bald";
validateName<ValidNames, typeof input1>(input1); // Ok.
validateName<ValidNames, typeof input2>(input2); // Error.
Solution #2: Adjust the function to require an extra argument.
function validateNameWith<N extends string, S extends Validate<N, S>>(_valid: N, input: S) {}
validateNameWith("" as ValidNames, "bold"); // Ok.
validateNameWith("" as ValidNames, "bald"); // Error.
Solution #3: Use closure technique - wrap the function inside another.
// Function to create a validator and insert N into it.
function createValidator<N extends string>() {
// Return the actual validator.
return function validateName<S extends Validate<N, S>>(input: S) {}
}
const validateMyName = createValidator<ValidNames>();
validateMyName("bold"); // Ok.
validateMyName("bald"); // Error.
Updated: Revised the functions by eliminating the ambiguous :N[]
part.
More Information / Context:
The aim is to develop a string validator for various uses, such as HTML class names. Most parts work seamlessly, except for the slightly complex syntax (refer to the 3 solutions provided above).
// Credits: https://github.com/microsoft/TypeScript/pull/40336
type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
[S];
// Type for validating a class name.
type ClassNameValidator<N extends string, S extends string, R = string> =
Split<S, " "> extends N[] ? R : never;
// Function for validating classes.
function validateClass<N extends string, S extends ClassNameValidator<N, S>>(input: S) {}
const test3 = "bold italic";
const test4 = "bald";
validateClass<ValidNames, typeof test3>(test3); // Ok.
validateClass<ValidNames, typeof test4>(test4); // Error.