3

Update: Added @babel/plugin-transform-named-capturing-groups-regex per @Jack Misteli suggestion to look at Babel.

Update:: Using Babel to transpile the code, I then tested in a CSB forked from @Jack Misteli's. The key here is that when I try to return groups, it's null CSB of returned vale for transpiled code is null


I'm trying to understand what it is about Typescript (my config, the language, etc.) that's breaking this regular expression.

Specifically, it appears to have to do with the named capture groups that I was using because when I remove them, it works.

Details:

Original TS file:

/**
 *
 * @param {string} markdownLink A link in the form of [title](link description)
 * @returns {object} An object with three keys: title, link, description
 */
export function parseLink(markdownLink: string) {
    const pattern: RegExp = new RegExp(
        /^(\[(?<title>[^\]]*)?\]\((?<link>[A-Za-z0-9\:\/\.\- ]+)(?<description>\"(.+)\")?\))/
    )

    const match = markdownLink.match(pattern)
    const groups = match?.groups
    return groups
}

This transpiled to (updated after adding the babel plugin):

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.parseLink = parseLink;

var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));

var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));

var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));

var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));

var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper"));

function _wrapRegExp(re, groups) { _wrapRegExp = function _wrapRegExp(re, groups) { return new BabelRegExp(re, undefined, groups); }; var _RegExp = (0, _wrapNativeSuper2["default"])(RegExp); var _super = RegExp.prototype; var _groups = new WeakMap(); function BabelRegExp(re, flags, groups) { var _this = _RegExp.call(this, re, flags); _groups.set(_this, groups || _groups.get(re)); return _this; } (0, _inherits2["default"])(BabelRegExp, _RegExp); BabelRegExp.prototype.exec = function (str) { var result = _super.exec.call(this, str); if (result) result.groups = buildGroups(result, this); return result; }; BabelRegExp.prototype[Symbol.replace] = function (str, substitution) { if (typeof substitution === "string") { var groups = _groups.get(this); return _super[Symbol.replace].call(this, str, substitution.replace(/\$<([^>]+)>/g, function (_, name) { return "$" + groups[name]; })); } else if (typeof substitution === "function") { var _this = this; return _super[Symbol.replace].call(this, str, function () { var args = []; args.push.apply(args, arguments); if ((0, _typeof2["default"])(args[args.length - 1]) !== "object") { args.push(buildGroups(args, _this)); } return substitution.apply(this, args); }); } else { return _super[Symbol.replace].call(this, str, substitution); } }; function buildGroups(result, re) { var g = _groups.get(re); return Object.keys(g).reduce(function (groups, name) { groups[name] = result[g[name]]; return groups; }, Object.create(null)); } return _wrapRegExp.apply(this, arguments); }

/**
 *
 * @param {string} markdownLink A link in the form of [title](link description)
 * @returns {object} An object with three keys: title, link, description
 */
function parseLink(markdownLink) {
  var pattern = new RegExp( /*#__PURE__*/_wrapRegExp(/^(\[([\0-\\\^-\uFFFF]*)?\]\(([ \.-:A-Za-z]+)("(.+)")?\))/, {
    title: 2,
    link: 3,
    description: 4
  }));
  var match = markdownLink.match(pattern);
  var groups = match === null || match === void 0 ? void 0 : match.groups;
  return groups;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ...fQ==

My tsconfig is:

{
    "compilerOptions": {

        /* Basic Options */
        "target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
        "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
        "sourceMap": true /* Generates corresponding '.map' file. */,
        "outDir": "dist" /* Redirect output structure to the directory. */,
        "composite": true /* Enable project compilation */,

        /* Strict Type-Checking Options */
        "strict": true /* Enable all strict type-checking options. */,

        /* Module Resolution Options */
        "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
        "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,

        /* Advanced Options */
        "skipLibCheck": true /* Skip type checking of declaration files. */,
        "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
        "resolveJsonModule": true
    },
    "include": ["./src"]
}

My .babelrc:

{
    "presets": ["@babel/preset-env", "@babel/preset-typescript"],
    "plugins": [
        "@babel/plugin-proposal-class-properties",
        "@babel/plugin-transform-runtime",
        "@babel/plugin-transform-named-capturing-groups-regex"
    ]
}

It's worth noting that the two patterns:

  • The one I wrote:
    ^(\[(?<title>[^\]]*)?\]\((?<link>[A-Za-z0-9\:\/\.\- ]+)(?<description>\"(.+)\")?\))
    
  • And the transpiled one
    /^(\[([\0-\\\^-\uFFFF]*)?\]\(([ \.-:A-Za-z]+)("(.+)")?\))/
    

Do not do the same thing (see the links for tests).

If I remove the named capture groups, it does seem to work, though I'm now struggling to understand why I have a duplicate full capture group.

debugging the parsedLink, i.e. the transpiled TS version

In the above image, you can see I'm comparing the native JS version with the imported, transpiled version (from the dist folder).

The debugger on the left shows the value of parsed, which is the variable assigned to the return of the transpiled version.

So - my questions:

  1. Is there something I need to do to enable named capture groups in TS? Update It's not related to TS, but Babel. The answer is to use @babel/plugin-transform-named-capturing-groups-regex
  2. Is there something I should know to understand why I'm getting a duplicate group for the transpiled version?
  3. Is there a better way to do this? :)
  4. What am I doing wrong with configuring the named capture groups plugin that it still doesn't return a match when it's present?
2
  • 1
    I'm not sure that first link matches up with the regex you posted. Commented Oct 2, 2020 at 19:49
  • Whoops! Thanks @zzzzBov! Fixed. Commented Oct 3, 2020 at 17:03

1 Answer 1

4

This is not a Typescript issue but a Babel issue. Named regular expressions are a recent feature that was introduced in ES2018. So in order to use it you must make sure that babel is being configured properly. That being said I think it is setup properly. The regex pattern created after the transpilation because we are creating a pattern that fits Babel's _wrapRegExp (not sure how it works internally). So you can't copy paste the pattern into https://regex101.com. The Regex engines are different so similar inputs will lead to different outputs.

The code samples you are showing are inconsistant (notice how you build your regex, the console logs are not showing). Howevever the main issue is that in your screenshot the logs are not the same as what is shown in the code.

None of the codes samples actually match the groups. So to make a proper comparison I created a CSB which only returns the match array.And it looks like everything is the same: https://codesandbox.io/s/wizardly-bird-jm74k?file=/src/index.js. I made a JS version of your Typescript, created a babelized version of that file, and used your Typescript transpiled version just returning the match.

All the results are the same for the following test string. So good news, I think everything is working well !

[test](http://google.com "hello")
[test](https://google.com "hello")
[test](google.com)
[](google.com)
[test](google.com "hello")
Sign up to request clarification or add additional context in comments.

4 Comments

🤔but in the CSB you are just returning the array, not the groups, right? The desired outcome is to be able to see a group. Also - I'm sure you're right, but I'm not following what was inconsistent in my example. Would love to update to make it right. Thanks again for looking!
Yes, I am returning the matched value because in your examples the groups are undefined (cf the screenshot). So it is hard to test the result of a function with an undefined return value. The inconsistencies I'm referring to come from the fact that the way you build your regex object is different in the Typescript version and the Javascript version. The console.logs are actually not shown in the left part of the screenshot. And this was fixed after @zzzzBov's comment but the regexes used in the playground were not the correct ones.
Also regarding @babel/plugin-transform-named-capturing-groups-regex I am not sure that's the answer. I use it in the babel REPL that I'm sharing but since the initial regex doesn't capture groups I wasn't able to test whether or not it helps with groups. But using your initial code only as a reference with or without it you get the exact same results/matches).
Yup! Totally understand. I think the confusion is that I'd like to use named capture groups. When I tried using the plugin, it seems to interpret the regex pattern fine, however, it only returns an array. The named capture groups are not present. Consequently, when I try to access .groups I get null. Example: codesandbox.io/s/throbbing-wildflower-vxj1q?file=/src/… (also updated the original question with a babel transpilation link)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.