Ways to enforce a specific type based on the provided parameter

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.

Answer №1

Here is a potential solution that may suit your needs. Instead of utilizing a Validation type, you can create a type that generates all permutations of possible values based on the given string union.

type AllPermutations<T extends string> = {
  [K in T]: 
    | `${K}${AllPermutations<Exclude<T, K>> extends infer U extends string 
        ? [U] extends [never] 
          ? "" 
          : ` ${U}` 
        : ""}` 
    | `${AllPermutations<Exclude<T, K>> extends infer U extends string 
        ? U 
        : never}`
}[T]

// Function for validating classes.
function validateClass<N extends string>(input: AllPermutations<N>) {}

It successfully passes the following set of tests.

type ValidNames = "bold" | "italic";

validateClass<ValidNames>("bold");  // Passes.
validateClass<ValidNames>("bold italic");  // Passes.
validateClass<ValidNames>("italic");  // Passes.
validateClass<ValidNames>("italic bold");  // Passes.
validateClass<ValidNames>("something else");  // Fails.

However, it's worth noting that as the union grows larger, performance may become an issue. I would recommend against using this approach with a larger union like ValidNames.

Playground

Answer №2

Resolution:

By chance, I stumbled upon a solution (or an elegant workaround). It follows the same core concept as workaround #3, but it is solely implemented in TypeScript without any additional JavaScript. The key approach is to use a "type closure" to separate the N and S components: essentially, defining a function with a generic N parameter on one side and utilizing the S exclusively on the other side.

// Defining a type for validation functions, separating N and S.
// .. Utilizing the ClassNameValidator specified in the original query.
type Validate<N extends string> = <S extends ClassNameValidator<N, S>>(input: S) => void;

// Subsequently, we can focus solely on N - excluding S.
// .. Notably, the actual JavaScript function can be reused (e.g., from a library).
const validateClass: Validate<ValidNames> = (_input) => {}

// Evaluation.
validateClass("bold");  // Success.
validateClass("italic bold");  // Success.
validateClass("bald");  // Failure.

It is important to highlight that this approach resolves the issue at hand for me - even though it may not perfectly align with the initial inquiry posed in the title (which appears to be currently unachievable).

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Obtain a union type in TypeScript based on the values of a field within another union type

Is it feasible in Typescript to derive a union type from the values of a field within another union type? type MyUnionType = | { foo: 'a', bar: 1 } | { foo: 'b', bar: 2 } | { foo: 'c', bar: 3 } // Is there an automati ...

Managing Visual Studio Code Extension Intellisense: A Guide

I am looking to create an extension I recommend using "CompletionList" for users. Users can trigger completion by running "editor.action.triggerSuggest" The process of my extensions is as follows: Users input text If they press the "completion command," ...

Bringing together the functionality of tap to dismiss keyboard, keyboard avoiding view, and a submit button

On my screen, there's a big image along with a text input and a button at the bottom. The screen has three main requirements: When the user taps on the input, both the input and button should be visible above the keyboard The user must be able to ta ...

What is the correct way to set up Typescript with external packages for Internet Explorer 11 using Babel 7 and Webpack 4?

After releasing a new version of our react app, we encountered an issue with IE11 complaining about the use of the let keyword. Upon investigation, we discovered that upgrading the query-string package from version 5.1.0 to 6.4.0 introduced the usage of th ...

Creating an Escape key press event in plain Typescript without using JQuery

Is there a way to trigger an Escape button press event in Angular unit tests? const event = document.createEvent('UIEvent'); event.initUIEvent('keydown', true, true, window, 0); (<any>event).keyCode = 27; (<any ...

Solving the issue of loading Ember Initializers during the transition to TypeScript

While following the ember quick start tutorial, I attempted to switch from Javascript to Typescript. Converting the .js files to .ts files resulted in an error with the ember-load-initializers import. The application is unable to run until this issue is re ...

minimize the size of the indigenous foundation input field

Currently incorporating this code snippet into my application: <Item fixedLabel> <Input style={{ width: 0.5 }}/> </Item> The fixedLabel element is extending the entire width of the screen. I have attempted adju ...

Deactivate multiple textareas within a loop by utilizing JQuery/Typescript and KnockoutJS

I am working on a for loop that generates a series of textareas with unique ids using KO data-binding, but they all share the same name. My goal is to utilize Jquery to determine if all the textareas in the list are empty. If they are, I want to disable al ...

Attempting to locate a method to update information post-editing or deletion in angular

Are there any methods similar to notifyDataSetChange() in Android Studio, or functions with similar capabilities? ...

What is the validity of using Promise.reject().catch(() => 5) in Typescript?

Can you explain why the TS compiler is not flagging an error for this specific code snippet? Promise.reject().catch(() => 5) Upon inspecting the definition of the handler function within the catch, we come across the following code: interface Promise&l ...

Dividing component files using TypeScript

Our team has embarked on a new project using Vue 3 for the front end. We have opted to incorporate TypeScript and are interested in implementing a file-separation approach. While researching, we came across this article in the documentation which provides ...

Is it possible for React props to include bespoke classes?

In our code, we have a TypeScript class that handles a variety of parameters including type, default/min/max values, and descriptions. This class is utilized in multiple parts of our application. As we switch to using React for our GUI development, one of ...

Retrieving a data type from the key values of deeply nested objects

I'm currently working with JSON data that contains nested objects, and my goal is to extract the unique set of second-level keys as a type. For instance: const json = { 'alice': { 'dogs': 1, 'birds': 4 ...

The React Functional Component undergoes exponential re-renders when there is a change in the array

I'm encountering a problem with one of my functional components. Essentially, it maintains an array of messages in the state; when a new message is received from the server, the state should update by adding that new message to the array. The issue ar ...

What is the best way to implement CSS Float in typescript programming?

For a specific purpose, I am passing CSS Float as props. To achieve this, I have to define it in the following way: type Props = { float: ???? } const Component = ({ float }: Props) => {......} What is the most effective approach to accomplish this? ...

Setting up WebPack for TypeScript with import functionality

A tutorial on webpack configuration for typescript typically demonstrates the following: const path = require('path'); module.exports = { ... } Is it more advantageous to utilize ES modules and configure it with import statements instead? Or is ...

Change the variable value within the service simultaneously from various components

There is a service called DisplaysService that contains a variable called moneys. Whenever I make a purchase using the buy button on the buy component, I update the value of this variable in the service from the component. However, the updated value does n ...

Comprehensive compilation of Typescript error codes along with solutions

Where can I find a comprehensive list of Typescript error codes and their corresponding fixes? I frequently encounter errors during compilation, such as: data_loader_service.ts(10,13): error TS1005: '=>' expected. data_loader_service.ts(10,24 ...

Disregarding TypeScript import errors within a monorepo ecosystem

In my Turborepo monorepo, I have a Next.js app package that imports various components from a shared package. This shared package is not compiled; it simply contains components imported directly by apps in the monorepo. The issue arises with the shared co ...

Is there a way to seamlessly transfer (optional) parameters from a CloudFormation template to a CDK resource within a CfnInclude without statically defining the list of parameters?

Trying to grasp these syntax rules, unsure if it's possible. We have numerous CloudFormation templates that we want to deploy using CDK by constructing them with CfnInclude. The issue is that CfnInclude always needs an explicit parameters argument if ...