npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@discretetom/r-compose

v0.2.2

Published

Compose RegExp in JavaScript in a readable and maintainable way.

Downloads

26

Readme

R-Compose

npm coverage build license

Compose RegExp in JavaScript in a readable and maintainable way.

Install

yarn add @discretetom/r-compose

Features

  • Treat RegExp as its source string, and concat it with literal or escaped strings. Empty strings will be ignored.
concat(
  /\n/, // => "\\n"
  "123", // => "123"
  false ? /123/ : "", // => ignored
  escape("()"), // => "\\(\\)"
) === "\\n123\\(\\)";
  • Auto group content in a non-capturing group if the composable function is context-sensitive.
select("a", "b") === "(?:a|b)";
optional("c") === "(?:c)?";
lookahead("d") === "(?=d)";
  • Additional options for some composable functions.
capture("a", { name: "foo" }) === "(?<foo>a)";
optional("a", { greedy: false }) === "(?:a)??";
lookahead("a", { negative: true }) === "(?!a)";
  • Composable functions return string so you can cascade / nest them.
lookahead(
  concat(
    lookbehind("[_$[:alnum:]]", { negative: true }),
    select(lookbehind(/\.\.\./), lookbehind(/\./, { negative: true })),
    optional(concat(capture(/\bexport/), /\s+/)),
    optional(concat(capture(/\bdeclare/), /\s+/)),
    concat(/\b/, capture(select("var", "let"))),
    lookahead("[_$[:alnum:]]", { negative: true }),
    select(lookahead(/\.\.\./), lookahead(/\./, { negative: true })),
  ),
);

When to Use

  • When to use concat? Should I always use concat('a', 'b', 'c') instead of directly /abc/?
    • Use concat when you need to concat strings with RegExp, since the escaped sequences in strings and in RegExps are different. E.g. concat('a', /\*/) === "a\\*".
    • Use concat if you have conditional logic, since empty strings will be ignored. E.g. concat('a', false ? 'b' : '', 'c') === "ac".
  • When to use those which will create groups like select/capture/optional/lookahead?
    • They are always recommended to use, since parentheses in TypeScript can be organized by code formatter and is more readable than in RegExp source string.
    • Unless the decorated content is only one character, e.g. transform (?:ab)? into optional('ab') is recommended since the parentheses can be removed, but a? is not since there is no parentheses.

Overall, it's your choice to use r-compose aggressively or conservatively. But you can always optimize the organization of your RegExp progressively.

Example

retsac use r-compose to avoid escape hell and make the code more readable and maintainable.

new RegExp(
  // open quote
  `(?:${esc4regex(open)})` +
    // content, non-greedy
    `(?:${
      escape
        ? `(?:${lineContinuation ? "\\\\\\n|" : ""}\\\\.|[^\\\\${
            multiline ? "" : "\\n"
          }])` // exclude `\n` if not multiline
        : `(?:${lineContinuation ? "\\\\\\n|" : ""}.${
            multiline
              ? // if multiline, accept `\n`
                "|\\n"
              : ""
          })`
    }*?)` + // '*?' means non-greedy(lazy)
    // close quote
    `(?:${
      acceptUnclosed
        ? // if accept unclosed, accept '$'(EOF)
          // or '\n'(if not multiline)
          `(?:${esc4regex(close)})|$${multiline ? "" : "|(?=\\n)"}`
        : esc4regex(close)
    })`,
);
compose(({ concat, any, select, lookahead, escape, not }) =>
  concat(
    // match open quote
    escape(open),
    // match content
    any(
      escaped
        ? select(
            lineContinuation ? /\\\n/ : "", // line continuation is treated as part of the content
            /\\./, // any escaped character is treated as part of the content
            not(
              // any character except the following is treated as part of the content
              concat(
                /\\/, // standalone backslash shouldn't be treated as part of the content
                multiline ? "" : /\n/, // if not multiline, `\n` shouldn't be treated as part of the content
              ),
            ),
          )
        : select(
            lineContinuation ? /\\\n/ : "", // line continuation is treated as part of the content
            /./, // any non-newline character is treated as part of the content
            multiline ? /\n/ : "", // if multiline, `\n` should be treated as part of the content
          ),
      // since we use `/./` in the content, we need to make sure it doesn't match the close quote
      { greedy: false },
    ),
    // match close quote
    acceptUnclosed
      ? select(
          escape(close),
          "$", // unclosed string is acceptable, so EOF is acceptable
          multiline
            ? "" // if multiline is enabled, we don't treat `\n` as the close quote
            : lookahead(/\n/), // use lookahead so we don't include the `\n` in the result
        )
      : escape(close), // unclosed string is not accepted, so we only accept the close quote
  ),
);

Source: https://github.com/DiscreteTom/retsac/commit/86b7ddf4a8c008086171b8d471dd05214327bfb3?diff=split

retsac also use r-compose to refactor long and complex regex with confidence.

Before

enableSeparator
  ? new RegExp(
      `(?:0x[\\da-f]+|0o[0-7]+|\\d+(?:${separator}\\d+)*(?:\\.\\d+(?:${separator}\\d+)*)?(?:[eE][-+]?\\d+(?:${separator}\\d+)*)?)${
        boundary ? "\\b(?!\\.)" : "" // '.' is not allowed as the boundary
      }`,
      "i",
    )
  : new RegExp(
      `(?:0x[\\da-f]+|0o[0-7]+|\\d+(?:\\.\\d+)?(?:[eE][-+]?\\d+)?)${
        boundary ? "\\b(?!\\.)" : "" // '.' is not allowed as the boundary
      }`,
      "i",
    );

After

compose(
  ({ concat, select, any, optional, lookahead }) => {
    const separatorPart = enableSeparator ? any(concat(separator, /\d+/)) : "";
    return concat(
      select(
        /0x[\da-f]+/, // hexadecimal
        /0o[0-7]+/, // octal
        // below is decimal with separator
        concat(
          /\d+/, // integer part
          separatorPart, // separator and additional integer part
          optional(concat(/\.\d+/, separatorPart)), // decimal part
          optional(concat(/[eE][-+]?\d+/, separatorPart)), // exponent part
        ),
      ),
      boundary
        ? concat(
            /\b/,
            // '.' match /\b/ but is not allowed as the boundary
            lookahead(/\./, { negative: true }),
          )
        : "",
    );
  },
  "i", // case insensitive
);

Source: https://github.com/DiscreteTom/retsac/commit/430a2175eb4c6d564ebdacf5b01a91ea42885ef2?diff=split

More Examples

CHANGELOG