Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trim as new intrinsic for use with template literal types #41283

Open
5 tasks done
hayes opened this issue Oct 27, 2020 · 9 comments
Open
5 tasks done

Add trim as new intrinsic for use with template literal types #41283

hayes opened this issue Oct 27, 2020 · 9 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@hayes
Copy link

hayes commented Oct 27, 2020

Search Terms

intrinsic
trim
whitespace

possible related: #41210

Suggestion

Add trim as a new intrinsic alongside the existing intrinsic types:
Uppercase
Lowercase
Capitalize
Uncapitalize

Use Cases

Trim can be implemented with the current types pretty easily by doing something like:

type Whitespace = '\n' | ' ';
type Trim<T> = T extends `${Whitespace}${infer U}` ? Trim<U> : T extends `${infer U}${Whitespace}` ? Trim<U> : T;

But this can quickly run into depth limits if there is a lot of repeated whitespace.

type Whitespace = | '\n'|  ' ' | 'x' // Add x as whitespace to make example easier to read
type Trim<T> = T extends `${Whitespace}${infer U}` ? Trim<U> : T extends `${infer U}${Whitespace}` ? Trim<U> : T;
type Test = Trim<`abc
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
`>

This can be improved by adding strings of repeated whitespace to the Whitespace type, but will still run into limitations:

type Whitespace = | '\n' | '      ' | '     ' | '    ' | '   ' | '  ' | ' '
type Trim<T> = T extends `${Whitespace}${infer U}` ? Trim<U> : T extends `${infer U}${Whitespace}` ? Trim<U> : T;

This seems like a good case for an intrinsic implementation since it is a fairly simple and common operation on strings.

One use case would be to correctly type template literal helpers that trim leading indent whitespace, but there are probably many cases where trimming whitespace from a string would be useful.

Examples

Trim<`

   abc

`> // 'abc'

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@Kingwl
Copy link
Contributor

Kingwl commented Oct 28, 2020

Related: #41210

@hayes
Copy link
Author

hayes commented Oct 30, 2020

This may or may or may not be my initial motivation, but I even if this specific use case is not a good reason, I think the underlying feature request still makes sense.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Nov 4, 2020
@RyanCavanaugh
Copy link
Member

Use cases more in line with the intended uses of template string literals would be nice

@hayes
Copy link
Author

hayes commented Nov 4, 2020

@RyanCavanaugh not sure if this is a better use case, but another thing I was playing around with in one of my projects that uses Trim. This is a bit simplified, and not very polished but:

https://www.typescriptlang.org/play?ts=4.1.0-beta#code/C4TwDgpgBAymA2BLYAeAKgGigVShAHsBAHYAmAzlOcAE6LEDmAfFALxRp6EkVQAGAEgDe9AGYQaUABIQAhqQC+w7EpHFxktLMTwFfKAH4oAbRnysAOitwkqLTqzYmAXSgAuE2mcAoUJCgAUgD29OhcRGSUAK7EANbEQQDuxMbOjuE8lNR0jCzsnAQRvMZiEtJypJZWpZra8D5QjYYmroWZHHXeTd3NZqRdPY0egkJ9UABkVLT0DKoqwsGh9vCOTHoDQ1AA5Fu+4NAA6gAWyBDkYLIAxtDsWwA6xFtQAD7b2wDce-5odAC26HkOBlIvxhMdTucrhBVDUcHpmj9EP8nO4gW0QSNYfMhOCiJDrvCjIjkSwPGhPn5oAAxHREGgAeSidmBvBi8SSKTSOEBBW4IJK6jKfSqFlhy1cRjG6N4uCMNPgdMZdjqq1RpgqIvliqZ6BV3OcHlSX2gABEIKQSHZAVqJEqUDZkOgsPdHkxnVsWNLKAKNOVzFArKLBbUdA0mpKKizKJjg1AAAo0CCiRD4VTElB9FiTbIzdaDRpGRbEFDGdOZkVCDb5xrGADSUHoUFiEBAQVEHVDZLqddafN4IwTSZTMNjABl6ND9EZx8RoF2dFXugouS6PYumh5ZwA3CRVzcQHc0bzeC2XeCyRNQUQxS7ARBBYhQC0W4jMr1THLMAAUACMgqQQDJABKDwzRfK1j0uB9qC4WRfgQG4n3NS0vz4KsjggeB4CCddGkSIIaHgfo+CA48gA

I can definitely see the argument that there might not be a good "intended" use case for Trim, while it is a very common operation on strings, the intent of these types was probably more along the lines of property manipulation rather than string manipulation which means there are far fewer places where you might run into white space.

The dedent function is something I have used in a lot of different places in node applications. I'm not sure how valuable having it typed like this is though. By itself, it probably doesn't help too much, other than cleaning up previews of variables defined using the function, since the usage of those strings after formatting is generally not expected to be typed with anything beyond just string. I could imagine some cases where you expect a string with some number of non-blank lines or something, but that is getting into pretty contrived territory.

@RyanCavanaugh
Copy link
Member

How would strings like that make it into the type system in the first place, though?

@hayes
Copy link
Author

hayes commented Nov 4, 2020

As I said above, that example is a bit contrived. dedent would be a utility function that can be used in combitation with template strings anywhere in the code base, and I have mostly used similar functions to format text from template string before showing them to a user eg:

function printHelpText() {
    const helpText = dedent(`
        Some command:
            details on how to use id
   `)

    process.stdout.wirte(helpText)
}

which would print something like:

Some command:
    details on how to use id

Since dedent is generic, the string would "make it into the type system" through inference. It wouldn't be particularly useful by itself though, and the only benefit in this case would be to give you a better tool tip when hovering over helpText. I am not arguing this is a good use case or example, and I see that this isn't really a helpful use case by itself, its just the other place where I had used Trim when I was playing around with potential use cases for new features in 4.1.

I could imagine a use case for having additional formatting functions, that may expect a multi-line string or string with other constraints that could be combined with this to produce a combination of constraints that do actually limit the valid strings you can use, but again I'm not sure how valuable that actually is as a real world use case.

I think its fair to table this, or close it until there are use cases with more real world value.

@stabai
Copy link

stabai commented Feb 3, 2023

I ran into a use case that I think would benefit from this today

I have arrays of string values that come from an external source, and then they get imported as constants like so:

const propertyTypes = [
  'Single-Family Home',
  'Multifamily Home',
  'Apartment',
  'Condo',
  'Townhome',
] as const;

type PropertyType = typeof propertyTypes[number];

I want to define a type for something that maps these values to an object, with each entry having a record of PropertyAttributes that get loaded from a database. For example:

const propertyTypeAttributes = {
  singleFamilyHome: {requiresAuthorization: true, escrowDays: 30}
  multifamilyHome: {...}
  apartment: {...}
  condo: {...}
  townhome: {...}
}

I can almost define this type as Record<Uncapitalize<PropertyType>, PropertyAttributes>, but all of the spaces/symbols in the constants that are invalid as field names are still there. If I had something that could remove all of those characters that are invalid identifiers, I could get around it that way.

As it is, I have two options:

  1. Use indexing notation to get access to the fields (which will return any instead of a compiler error if the value passed to the indexer is invalid)
  2. Define enums for each of these value sets that maps them to valid identifiers

Here's a playground working with this concept:
https://www.typescriptlang.org/play?noImplicitAny=false&ssl=11&ssc=11&pln=11&pc=29#code/MYewdgzgLgBADgJxHApgqBPAKh1EYC8MA2gFAwwDkAygJZgDmANigLQBiAhgLa1MYwAEiG4pKAGnJUAsgFcmUWgDMefAcNESplAIJxO6UWChaKlAMLgAJiFNUsIAO5gAFiLGSAujE75QkKABuUlJMVBgABSRUdGxcFEIYMJQQJXhotEwcPGIwWW4AIzRPYNJ6KDQVYASo5EyMHSgoBFoC2Qr8AG8pCgQUAEdZWj6IHXa3FoAvTkVwAC4YApAQFk4wYIoKFAhgJEcAEU4MCAW8wrRggF8Q-2h0utjslEbm1vbtxO7NmAAiCHpmGwuLx+EJ3D8Fp1Lj58LUYpgXi02h1JN8ftx5IoVCD1ODIdDfJEMrFEW8UT0fPpDChjPiYUSHgimkj3hBUZt-DY6YS4fVScjtuyKFAnK53NzYcSma8BWzSAS-OA7hAZrQIEpaB8AEooUAIKwAHgAqmBgJw4LQoJwmLRJigDbzHvEAHziBnwhrMsnbZ2lAD0fpgWBcapgjhACAA1mz6ZaYBA3PIrHNSLdYJyQABRHZ7Q7HRKIRlxVD81kAOgzZe2uyceYg-sDwdD4ajMcJcYTICTMAAFMiYGqwJRYLQrChrQBKFNp+MAlgaFDZmsHI74IiFj1PUsdYiUf6MFgcVSgheUTxVnO11cNoMh-At6NulAANxpSUTDBcA9gnaTQ9gPZ9FAsgIJAA7cHANrAHGawYFOqZKrABRMJwLhLrmq4FlKxbPF6sq7shqFnhey51jeTb4DY2z-mGEaRm6-ZxuG3ZFDAYC0NUA5pHGVijtOiEwBiCi0Au6FXvm67YVueHlvugLAmoC4kRhxzBEAA

@kiliancs
Copy link

We are adding additional type safety to i18next's t function so that only valid keys may be used, and to require the interpolation parameters for the given key.

The following syntax are all valid:

  • Some {{val}}
  • Some {{ val}}
  • Some {{ val }}
  • Some {{val, number(minimumFractionDigits: 2)}}
  • Some {{val , number(minimumFractionDigits: 2)}}

As you can see, having a built-in way (without having to worry about limitations described in this issue's description) to trim whitespace would greatly simplify handling all the cases.

@saiichihashimoto
Copy link

I am attempting to parse sanity's groq strings in @sanity-typed using typescript and it's working perfectly with string literals... except for all the whitespace. Essentially, the same issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants