Skip to content

RegExp literal flag types #38671

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

Open
5 tasks done
RussellSprouts opened this issue May 20, 2020 · 2 comments
Open
5 tasks done

RegExp literal flag types #38671

RussellSprouts opened this issue May 20, 2020 · 2 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

@RussellSprouts
Copy link

Search Terms

RegExp Regex literal flags as const narrowing

Suggestion

This is similar to the proposal for typing named capture groups #32098
It would be useful to narrow RegExp literals based on their flags as well. This could be modeled as type parameter(s) as in that proposal, or as a type intersection, which I will use here.

const myRegExp = /hello/y; // has type RegExp & {dotAll: false, global: false, ignoreCase: false, multiline: false, sticky: true, unicode: false, flags: 'y'};

Use Cases

Regular expressions have different behavior when defined with different flags, and functions may have different behavior when they receive them, so they may want to distinguish the flags of a RegExp for overloads or allowed argument values.

Examples

In the standard library, String.prototype.match returns either the first match with its capture groups, or a list of all matches, depending on whether the argument is a global RegExp. This could be modeled as an overload:

match(str: string): RegExpMatchArray|null
match(regexp: RegExp & {global: false}): RegExpMatchArray|null
match(regexp: RegExp & {global: true}): Array<string>|null;
match(regexp: RegExp): RegExpMatchArray|null;

I recently wrote some functions which only work with sticky RegExps, so I had to put checks at the top of each function:

function match(regexp: RegExp) {
    if (!regexp.sticky) { throw new Error("Invalid argument -- RegExp must use the /y 'sticky' flag"); }
   ...
}

match(/sticky example/y); // compiles and runs fine
match(/non-sticky example/); // compiles fine, runtime error

I'd like to define the function instead as:

type StickyRegExp = RegExp & {sticky: true};
function match(regexp: StickyRegExp) {
   ...
}

match(/sticky example/y); // compiles and runs fine
match(/non-sticky example/); // compiler error

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.
@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 Jun 9, 2020
@webstrand
Copy link
Contributor

I've implemented the as const modifier for RegExp literals (https://github.com/webstrand/TypeScript/tree/regexp-literal-flags). This does not include support for const foo = /foo/g receiving the type RegExp & { global: true, ignoreCase: false, multiline: false, ... }. Since as const was not previously permitted on RegExp literals, this is not a breaking change.

I played around with adapting the type-freshness system to include regular expression literals, but it looks like that system was never designed to interact with non-literal types. Changing this would involve either adding regexps as a primitive type or comprehensive changes to the freshness system to allow non-literal types to have fresh types.

I would be willing to continue to work on this feature, if there's any continuing interest in it? I'll probably need a little guidance, so I don't make a mess of things. It looks like this needs to be added to the backlog, before I can make a pull request.

@Tyler-Murphy
Copy link

This would also be useful for String.prototype.replaceAll, which throws a runtime error if the first argument is a non-global regular expression. See eslint/eslint#15073 for more context.

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

4 participants