The Wayback Machine - https://web.archive.org/web/20201216025626/https://github.com/microsoft/TypeScript/issues/41160
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

Regex-validated string types (feedback reset) #41160

Open
RyanCavanaugh opened this issue Oct 19, 2020 · 7 comments
Open

Regex-validated string types (feedback reset) #41160

RyanCavanaugh opened this issue Oct 19, 2020 · 7 comments

Comments

@RyanCavanaugh
Copy link
Member

@RyanCavanaugh RyanCavanaugh commented Oct 19, 2020

This is a pickup of #6579. With the addition of #40336, a large number of those use cases have been addressed, but possibly some still remain.

Search Terms

regex string types

Suggestion

Open question: For people who had upvoted #6579, what use cases still need addressing?

Note: Please keep discussion on-topic; moderation will be a bit heavier to avoid off-topic tangents

Examples

(please help)

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.
@AnyhowStep
Copy link
Contributor

@AnyhowStep AnyhowStep commented Oct 19, 2020

Use case 1, URL path building libraries,

/*snip*/
createTestCard : f.route()
    .append("/platform")
    .appendParam(s.platform.platformId, /\d+/)
    .append("/stripe")
    .append("/test-card")
/*snip*/

These are the constraints for .append(),

  • ✔️ Must start with leading forward slash (/)
  • Must not end with trailing forward slash (/)
  • Must not contain colon character (:); it is reserved for parameters
  • Must not contain two, or more, forward slashes consecutively (//)

Use case 2,

  • Hexadecimal/binary/decimal/etc. strings of non-trivial length (explosion of union types)

Use case 3, safer RegExp constructor (and similar functions?),

new(pattern: string, flags?: PatternOf</^[gimsuy]*$/>): RegExp
  • flags should only contain the characters g,i,m,s,u,y
  • Each character should only be used once (To be fair, this condition would be hard for regexes, too, requiring negative lookahead or many states)
  • Characters can be specified in any order
@yume-chan
Copy link
Contributor

@yume-chan yume-chan commented Oct 20, 2020

Template string type can only be used in conditional type, so it's really a "type validator", not a "type" itself. It also focuses more on manipulating strings, I think it's a different design goal from Regex-validated types.

It's doable to use conditional types to constrain parameters, for example taken from #6579 (comment)

declare function takesOnlyHex<StrT extends string> (
    hexString : Accepts<HexStringLen6, StrT> extends true ? StrT : {__err : `${StrT} is not a hex-string of length 6`}
) : void;

However I think this parttern has several issues:

  1. It's not a common pattern, and cumbersome to repeat every time.
  2. The type parameter should be inferred, but was used in a condition before it "can" be inferred, which is unintuitive.
  3. TypeScript still doesn't support partial generic inferrence (#26349) so it may be hard to use this pattern with more generic parameters.
@bmix
Copy link

@bmix bmix commented Oct 21, 2020

Would this allow me to define type constraints for String to match the XML specification's Name constructs (short summary) and QNames by expressing them as regular expressions? If so, I am all for it :-)

@ksabry
Copy link

@ksabry ksabry commented Oct 21, 2020

@AnyhowStep It isn't the cleanest, but with conditional types now allowing recursion, it seems we can accomplish these cases with template literal types: playground link

@AnyhowStep
Copy link
Contributor

@AnyhowStep AnyhowStep commented Oct 22, 2020

We can have compile-time regular expressions now.
But anything requiring conditional types and a generic type param to check is a non-feature to me.

(Well, non-feature when I'm trying to use TypeScript for work. All personal projects have --noEmit enabled because real TS programmers execute in compile-time)

@arcanis
Copy link

@arcanis arcanis commented Dec 12, 2020

Open question: For people who had upvoted #6579, what use cases still need addressing?

We have a strongly-typed filesystem library, where the user is expected to manipulate "clean types" like Filename or PortablePath versus literal strings (they currently obtain those types by using the as operator on literals, or calling a validator for user-provided strings):

export interface PathUtils {
  cwd(): PortablePath;

  normalize(p: PortablePath): PortablePath;
  join(...paths: Array<PortablePath | Filename>): PortablePath;
  resolve(...pathSegments: Array<PortablePath | Filename>): PortablePath;
  isAbsolute(path: PortablePath): boolean;
  relative(from: PortablePath, to: PortablePath): P;
  dirname(p: PortablePath): PortablePath;
  basename(p: PortablePath, ext?: string): Filename;
  extname(p: PortablePath): string;

  readonly sep: PortablePath;
  readonly delimiter: string;

  parse(pathString: PortablePath): ParsedPath<PortablePath>;
  format(pathObject: FormatInputPathObject<PortablePath>): PortablePath;

  contains(from: PortablePath, to: PortablePath): PortablePath | null;
}

I'm investigating template literals to remove the as syntax, but I'm not sure we'll be able to use them after all:

  • They don't raise errors very well
  • Interfaces are a pain to type (both declaration and implementation would have to be generics)
  • More generally, we would have to migrate all our existing functions to become generics, and our users would have too

The overhead sounds overwhelming, and makes it likely that there are side effects that would cause problems down the road - causing further pain if we need to revert. Ideally, the solution we're looking for would leave the code above intact, we'd just declare PortablePath differently.

@RyanCavanaugh
Copy link
Member Author

@RyanCavanaugh RyanCavanaugh commented Dec 14, 2020

@arcanis it really sounds like you want nominal types (#202), since even if regex types existed, you'd still want the library consumer to go through the validator functions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants
You can’t perform that action at this time.