question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Type named capture groups better

See original GitHub issue

Search Terms

named regexp, named capture groups

Motivation

Currently named capture groups are a bit of a pain in TypeScript:

  1. All names are of type string even if the regexp doesn’t have that named group.
  2. You need to use non-null assertion for .groups even when that is the only possibility.

Suggestion

I propose making RegExp higher order on its named capture groups so that .groups is well typed.

// Would have type: RegExp<{ year: string, month: string }>
const date = /(?<year>[0-9]{4})-(?<month>[0-9]{2})/

const match = someString.match(date)
if (match) {
  // match.groups type would be { year: string, month: string }
  // currently is undefined | { [key: string]: string }
}

// Would have type RegExp<{ year: string, month?: string }>
const optionalMonth = /(?<year>[0-9]{4})(-(?<month>[0-9]{2}))?/

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.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:49
  • Comments:15 (4 by maintainers)

github_iconTop GitHub Comments

5reactions
hlovdalcommented, Sep 17, 2021

For everyone that wants to have type safety and auto completion on the groups part right now you can declare the variable the match result is stored in as the RegExpMatchArrayWithGroups type below like the following:

const output = 'hello_world.ts:13412:Missing ;.';
const m: RegExpMatchArrayWithGroups<{file: string, line: string; error: string}>
    = output.match(/^(?<file>[^:]+):(?<line>[^:]+):(?<error>.*)/);
if (m && m.groups) {
    // f: "hello_world.ts", l: "13412", e: "Missing ;."
    console.log('f: "' + m.groups.file + '", l: "' + m.groups.line + '", e: "' + m.groups.error + '"');

    // console.log(m.groups.filename); 
    // Property 'filename' does not exist on type '{ file: string; line: string; error: string; }'
}

The RegExpMatchArrayWithGroups type needs a type argument where all the group names in the regex are duplicated, so there is no automatic parsing from it, but when things are in the same statement this should be quite maintainable.

Definitions:

type RegExpMatchArrayWithGroupsOnly<T> = {
    groups?: {
        // eslint-disable-next-line no-unused-vars
        [key in keyof T]: string;
    }
}
type RegExpMatchArrayWithGroups<T> = (RegExpMatchArray & RegExpMatchArrayWithGroupsOnly<T>) | null;
3reactions
Pyrolisticalcommented, Aug 11, 2022

Thanks @hlovdal. I took your idea made it easier to use.

type RegExpGroups<T extends string[]> =
  | (RegExpMatchArray & {
      groups?:
        | {
            [name in T[number]]: string;
          }
        | {
            [key: string]: string;
          };
    })
  | null;

const output = "hello_world.ts:13412:Missing ;.";
const match: RegExpGroups<["file", "line", "error"]> = output.match(
  /^(?<file>[^:]+):(?<line>[^:]+):(?<error>.*)/
);
if (match) {
  const { file, line, error } = match.groups!;
  console.log({ file, line, error });
}

Playground

Read more comments on GitHub >

github_iconTop Results From Across the Web

Groups and backreferences - JavaScript - MDN Web Docs
Named capturing group : Matches "x" and stores it on the groups property of the returned matches under the name specified by <Name>...
Read more >
New JavaScript Features That Will Change How You Write ...
Named capture groups use a more expressive syntax compared to regular capture groups. The s ( dotAll ) flag changes the behavior of...
Read more >
Named capturing groups in JavaScript regex? - Stack Overflow
There are only two "structural" advantages of named capturing groups I can think of: In some regex flavors (.NET and JGSoft, as far...
Read more >
Groups & Capturing Groups - Regular Expressions Basics
Use (?: ) for non capturing groups. If you need to use a group as a block but you won't process the results...
Read more >
Grouping Constructs in Regular Expressions - Microsoft Learn
A capturing group has a default name that is identical to its ordinal number. For more information, see Named matched subexpressions later ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found