├── .babelrc ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── publish-on-tag.yml ├── .gitignore ├── .travis.yml ├── LICENSE-MIT.txt ├── README.md ├── bin └── regexpu ├── man └── regexpu.1 ├── package.json ├── regexpu.js ├── scripts └── build ├── tests └── tests.js ├── transform-tree.js └── transpile-code.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babili"], 3 | comments: false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [README.md] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/publish-on-tag.yml: -------------------------------------------------------------------------------- 1 | name: publish-on-tag 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Install dependencies 15 | run: npm install 16 | - name: Build 17 | run: npm run build 18 | - name: Test 19 | run: npm test 20 | - name: Publish 21 | env: 22 | NPM_TOKEN: ${{secrets.NPM_TOKEN}} 23 | run: | 24 | npm config set registry 'https://wombat-dressing-room.appspot.com/' 25 | npm config set '//wombat-dressing-room.appspot.com/:_authToken' '${NPM_TOKEN}' 26 | npm publish 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Coverage report 4 | coverage 5 | 6 | # Installed npm modules 7 | node_modules 8 | 9 | # Folder view configuration files 10 | .DS_Store 11 | Desktop.ini 12 | 13 | # Thumbnail cache files 14 | ._* 15 | Thumbs.db 16 | 17 | # Files that might appear on external disks 18 | .Spotlight-V100 19 | .Trashes 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '10' 6 | - '12' 7 | script: 8 | - 'npm test' 9 | after_script: 10 | - 'istanbul cover --report html node_modules/.bin/_mocha tests -- -u exports -R spec && codecov' 11 | git: 12 | depth: 1 13 | -------------------------------------------------------------------------------- /LICENSE-MIT.txt: -------------------------------------------------------------------------------- 1 | Copyright Mathias Bynens 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # regexpu [![Build status](https://travis-ci.org/mathiasbynens/regexpu.svg?branch=main)](https://travis-ci.org/mathiasbynens/regexpu) [![Code coverage status](https://img.shields.io/codecov/c/github/mathiasbynens/regexpu.svg)](https://codecov.io/gh/mathiasbynens/regexpu) [![regexpu on npm](https://img.shields.io/npm/v/regexpu)](https://www.npmjs.com/package/regexpu) 2 | 3 | _regexpu_ is a source code transpiler that enables the use of ES2015 Unicode regular expressions in JavaScript-of-today (ES5). It rewrites regular expressions that make use of [the ES2015 `u` flag](https://mathiasbynens.be/notes/es6-unicode-regex) into equivalent ES5-compatible regular expressions. 4 | 5 | [Here’s an online demo.](https://mothereff.in/regexpu) 6 | 7 | [Traceur v0.0.61+](https://github.com/google/traceur-compiler), [Babel v1.5.0+](https://github.com/babel/babel), [esnext v0.12.0+](https://github.com/esnext/esnext), and [Bublé v0.12.0+](https://buble.surge.sh/) use _regexpu_ for their `u` regexp transpilation. The REPL demos for [Traceur](https://google.github.io/traceur-compiler/demo/repl.html#%2F%2F%20Traceur%20now%20uses%20regexpu%20%28https%3A%2F%2Fmths.be%2Fregexpu%29%20to%20transpile%20regular%0A%2F%2F%20expression%20literals%20that%20have%20the%20ES2015%20%60u%60%20flag%20set%20into%20equivalent%20ES5.%0A%0A%2F%2F%20Match%20any%20symbol%20from%20U%2B1F4A9%20PILE%20OF%20POO%20to%20U%2B1F4AB%20DIZZY%20SYMBOL.%0Avar%20regex%20%3D%20%2F%5B%F0%9F%92%A9-%F0%9F%92%AB%5D%2Fu%3B%20%2F%2F%20Or%2C%20%60%2F%5Cu%7B1F4A9%7D-%5Cu%7B1F4AB%7D%2Fu%60.%0Aconsole.log%28%0A%20%20regex.test%28'%F0%9F%92%A8'%29%2C%20%2F%2F%20false%0A%20%20regex.test%28'%F0%9F%92%A9'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AA'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AB'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AC'%29%20%20%2F%2F%20false%0A%29%3B%0A%0A%2F%2F%20See%20https%3A%2F%2Fmathiasbynens.be%2Fnotes%2Fes6-unicode-regex%20for%20more%20examples%20and%0A%2F%2F%20info.%0A), [Babel](https://babeljs.io/repl/#?experimental=true&playground=true&evaluate=true&code=%2F%2F%20Babel%20now%20uses%20regexpu%20%28https%3A%2F%2Fmths.be%2Fregexpu%29%20to%20transpile%20regular%0A%2F%2F%20expression%20literals%20that%20have%20the%20ES2015%20%60u%60%20flag%20set%20into%20equivalent%20ES5.%0A%0A%2F%2F%20Match%20any%20symbol%20from%20U%2B1F4A9%20PILE%20OF%20POO%20to%20U%2B1F4AB%20DIZZY%20SYMBOL.%0Avar%20regex%20%3D%20%2F%5B%F0%9F%92%A9-%F0%9F%92%AB%5D%2Fu%3B%20%2F%2F%20Or%2C%20%60%2F%5Cu%7B1F4A9%7D-%5Cu%7B1F4AB%7D%2Fu%60.%0Aconsole.log%28%0A%20%20regex.test%28'%F0%9F%92%A8'%29%2C%20%2F%2F%20false%0A%20%20regex.test%28'%F0%9F%92%A9'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AA'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AB'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AC'%29%20%20%2F%2F%20false%0A%29%3B%0A%0A%2F%2F%20See%20https%3A%2F%2Fmathiasbynens.be%2Fnotes%2Fes6-unicode-regex%20for%20more%20examples%20and%0A%2F%2F%20info.%0A), [esnext](https://esnext.github.io/esnext/#%2F%2F%20esnext%20now%20uses%20regexpu%20%28https%3A%2F%2Fmths.be%2Fregexpu%29%20to%20transpile%20regular%0A%2F%2F%20expression%20literals%20that%20have%20the%20ES2015%20%60u%60%20flag%20set%20into%20equivalent%20ES5.%0A%0A%2F%2F%20Match%20any%20symbol%20from%20U%2B1F4A9%20PILE%20OF%20POO%20to%20U%2B1F4AB%20DIZZY%20SYMBOL.%0Avar%20regex%20%3D%20%2F%5B%F0%9F%92%A9-%F0%9F%92%AB%5D%2Fu%3B%20%2F%2F%20Or%2C%20%60%2F%5Cu%7B1F4A9%7D-%5Cu%7B1F4AB%7D%2Fu%60.%0Aconsole.log%28%0A%20%20regex.test%28'%F0%9F%92%A8'%29%2C%20%2F%2F%20false%0A%20%20regex.test%28'%F0%9F%92%A9'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AA'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AB'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AC'%29%20%20%2F%2F%20false%0A%29%3B%0A%0A%2F%2F%20See%20https%3A%2F%2Fmathiasbynens.be%2Fnotes%2Fes6-unicode-regex%20for%20more%20examples%20and%0A%2F%2F%20info.%0A), and [Bublé](https://buble.surge.sh/#%2F%2F%20Bubl%C3%A9%20now%20uses%20regexpu%20%28https%3A%2F%2Fmths.be%2Fregexpu%29%20to%20transpile%20regular%0A%2F%2F%20expression%20literals%20that%20have%20the%20ES2015%20%60u%60%20flag%20set%20into%20equivalent%20ES5.%0A%0A%2F%2F%20Match%20any%20symbol%20from%20U%2B1F4A9%20PILE%20OF%20POO%20to%20U%2B1F4AB%20DIZZY%20SYMBOL.%0Avar%20regex%20%3D%20%2F%5B%F0%9F%92%A9-%F0%9F%92%AB%5D%2Fu%3B%20%2F%2F%20Or%2C%20%60%2F%5Cu%7B1F4A9%7D-%5Cu%7B1F4AB%7D%2Fu%60.%0Aconsole.log%28%0A%20%20regex.test%28'%F0%9F%92%A8'%29%2C%20%2F%2F%20false%0A%20%20regex.test%28'%F0%9F%92%A9'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AA'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AB'%29%2C%20%2F%2F%20true%0A%20%20regex.test%28'%F0%9F%92%AC'%29%20%20%2F%2F%20false%0A%29%3B%0A%0A%2F%2F%20See%20https%3A%2F%2Fmathiasbynens.be%2Fnotes%2Fes6-unicode-regex%20for%20more%20examples%0A%2F%2F%20and%20info.%0A) let you try `u` regexps as well as other ES.next features. 8 | 9 | ## Example 10 | 11 | Consider a file named `example-es2015.js` with the following contents: 12 | 13 | ```js 14 | var string = 'foo💩bar'; 15 | var match = string.match(/foo(.)bar/u); 16 | console.log(match[1]); 17 | // → '💩' 18 | 19 | // This regex matches any symbol from U+1F4A9 to U+1F4AB, and nothing else. 20 | var regex = /[\u{1F4A9}-\u{1F4AB}]/u; 21 | // The following regex is equivalent. 22 | var alternative = /[💩-💫]/u; 23 | console.log([ 24 | regex.test('a'), // false 25 | regex.test('💩'), // true 26 | regex.test('💪'), // true 27 | regex.test('💫'), // true 28 | regex.test('💬') // false 29 | ]); 30 | ``` 31 | 32 | Let’s transpile it: 33 | 34 | ```bash 35 | $ regexpu < example-es2015.js > example-es5.js 36 | ``` 37 | 38 | `example-es5.js` can now be used in ES5 environments. Its contents are as follows: 39 | 40 | ```js 41 | var string = 'foo💩bar'; 42 | var match = string.match(/foo((?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))bar/); 43 | console.log(match[1]); 44 | // → '💩' 45 | 46 | // This regex matches any symbol from U+1F4A9 to U+1F4AB, and nothing else. 47 | var regex = /(?:\uD83D[\uDCA9-\uDCAB])/; 48 | // The following regex is equivalent. 49 | var alternative = /(?:\uD83D[\uDCA9-\uDCAB])/; 50 | console.log([ 51 | regex.test('a'), // false 52 | regex.test('💩'), // true 53 | regex.test('💪'), // true 54 | regex.test('💫'), // true 55 | regex.test('💬') // false 56 | ]); 57 | ``` 58 | 59 | ## Known limitations 60 | 61 | 1. _regexpu_ only transpiles regular expression _literals_, so things like `RegExp('…', 'u')` are not affected. 62 | 2. _regexpu_ doesn’t polyfill [the `RegExp.prototype.unicode` getter](https://mths.be/es6#sec-get-regexp.prototype.unicode) because it’s not possible to do so without side effects. 63 | 3. _regexpu_ doesn’t support [canonicalizing the contents of back-references in regular expressions with both the `i` and `u` flag set](https://github.com/mathiasbynens/regexpu/issues/4), since that would require transpiling/wrapping strings. 64 | 4. _regexpu_ [doesn’t match lone low surrogates accurately](https://github.com/mathiasbynens/regexpu/issues/17). Unfortunately that is impossible to implement due to the lack of lookbehind support in JavaScript regular expressions. 65 | 66 | ## Installation 67 | 68 | To use _regexpu_ programmatically, install it as a dependency via [npm](https://www.npmjs.com/): 69 | 70 | ```bash 71 | npm install regexpu --save-dev 72 | ``` 73 | 74 | To use the command-line interface, install _regexpu_ globally: 75 | 76 | ```bash 77 | npm install regexpu -g 78 | ``` 79 | 80 | ## API 81 | 82 | ### `regexpu.version` 83 | 84 | A string representing the semantic version number. 85 | 86 | ### `regexpu.rewritePattern(pattern, flags, options)` 87 | 88 | This is an alias for the `rewritePattern` function exported by [_regexpu-core_](https://github.com/mathiasbynens/regexpu-core). Please refer to that project’s documentation for more information. 89 | 90 | `regexpu.rewritePattern` uses [regjsgen](https://github.com/d10/regjsgen), [regjsparser](https://github.com/jviereck/regjsparser), and [regenerate](https://github.com/mathiasbynens/regenerate) as internal dependencies. If you only need this function in your program, it’s better to include it directly: 91 | 92 | ```js 93 | // Instead of… 94 | const rewritePattern = require('regexpu').rewritePattern; 95 | 96 | // Use this: 97 | const rewritePattern = require('regexpu-core'); 98 | ``` 99 | 100 | This prevents the [Recast](https://github.com/benjamn/recast) and [Esprima](https://github.com/ariya/esprima) dependencies from being loaded into memory. 101 | 102 | ### `regexpu.transformTree(ast, options)` or its alias `regexpu.transform(ast, options)` 103 | 104 | This function accepts an abstract syntax tree representing some JavaScript code, and returns a transformed version of the tree in which any regular expression literals that use the ES2015 `u` flag are rewritten in ES5. 105 | 106 | ```js 107 | const regexpu = require('regexpu'); 108 | const recast = require('recast'); 109 | const tree = recast.parse(code); // ES2015 code 110 | const transformedTree = regexpu.transform(tree); 111 | const result = recast.print(transformedTree); 112 | console.log(result.code); // transpiled ES5 code 113 | console.log(result.map); // source map 114 | ``` 115 | 116 | The optional `options` object is passed to _regexpu-core_’s `rewritePattern`. For a description of the available options, [see its documentation](https://github.com/mathiasbynens/regexpu-core#rewritepatternpattern-flags-options). 117 | 118 | `regexpu.transformTree` uses [Recast](https://github.com/benjamn/recast), [regjsgen](https://github.com/d10/regjsgen), [regjsparser](https://github.com/jviereck/regjsparser), and [regenerate](https://github.com/mathiasbynens/regenerate) as internal dependencies. If you only need this function in your program, it’s better to include it directly: 119 | 120 | ```js 121 | const transformTree = require('regexpu/transform-tree'); 122 | ``` 123 | 124 | This prevents the [Esprima](https://github.com/ariya/esprima) dependency from being loaded into memory. 125 | 126 | ### `regexpu.transpileCode(code, options)` 127 | 128 | This function accepts a string representing some JavaScript code, and returns a transpiled version of this code tree in which any regular expression literals that use the ES2015 `u` flag are rewritten in ES5. 129 | 130 | ```js 131 | const es2015 = 'console.log(/foo.bar/u.test("foo💩bar"));'; 132 | const es5 = regexpu.transpileCode(es2015); 133 | // → 'console.log(/foo(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uDC00-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF])bar/.test("foo💩bar"));' 134 | ``` 135 | 136 | The optional `options` object recognizes the following properties: 137 | 138 | * `sourceFileName`: a string representing the file name of the original ES2015 source file. 139 | * `sourceMapName`: a string representing the desired file name of the source map. 140 | * `dotAllFlag`: a boolean indicating whether to enable [experimental support for the `s` (`dotAll`) flag](https://github.com/mathiasbynens/es-regexp-dotall-flag). 141 | * `unicodePropertyEscape`: a boolean indicating whether to enable [experimental support for Unicode property escapes](https://github.com/mathiasbynens/regexpu-core/blob/master/property-escapes.md). 142 | 143 | The `sourceFileName` and `sourceMapName` properties must be provided if you want to generate source maps. 144 | 145 | ```js 146 | const result = regexpu.transpileCode(code, { 147 | 'sourceFileName': 'es2015.js', 148 | 'sourceMapName': 'es2015.js.map', 149 | }); 150 | console.log(result.code); // transpiled source code 151 | console.log(result.map); // source map 152 | ``` 153 | 154 | `regexpu.transpileCode` uses [Esprima](https://github.com/ariya/esprima), [Recast](https://github.com/benjamn/recast), [regjsgen](https://github.com/d10/regjsgen), [regjsparser](https://github.com/jviereck/regjsparser), and [regenerate](https://github.com/mathiasbynens/regenerate) as internal dependencies. If you only need this function in your program, feel free to include it directly: 155 | 156 | ```js 157 | const transpileCode = require('regexpu/transpile-code'); 158 | ``` 159 | 160 | ## Transpilers that use regexpu internally 161 | 162 | If you’re looking for a general-purpose ES.next-to-ES5 transpiler with support for Unicode regular expressions, consider using one of these: 163 | 164 | * [Traceur](https://github.com/google/traceur-compiler) v0.0.61+ 165 | * [Babel](https://github.com/babel/babel) v1.5.0+ 166 | * [esnext](https://github.com/esnext/esnext) v0.12.0+ 167 | * [Bublé](https://gitlab.com/Rich-Harris/buble) v0.12.0+ 168 | 169 | ## For maintainers 170 | 171 | ### How to publish a new release 172 | 173 | 1. On the `main` branch, bump the version number in `package.json`: 174 | 175 | ```sh 176 | npm version patch -m 'Release v%s' 177 | ``` 178 | 179 | Instead of `patch`, use `minor` or `major` [as needed](https://semver.org/). 180 | 181 | Note that this produces a Git commit + tag. 182 | 183 | 1. Push the release commit and tag: 184 | 185 | ```sh 186 | git push && git push --tags 187 | ``` 188 | 189 | Our CI then automatically publishes the new release to npm. 190 | 191 | ## Author 192 | 193 | | [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | 194 | |---| 195 | | [Mathias Bynens](https://mathiasbynens.be/) | 196 | 197 | ## License 198 | 199 | _regexpu_ is available under the [MIT](https://mths.be/mit) license. 200 | -------------------------------------------------------------------------------- /bin/regexpu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const jsesc = require('jsesc'); 6 | const regexpu = require('../regexpu.js'); 7 | const strings = process.argv.splice(2); 8 | const stdin = process.stdin; 9 | let isFlags = false; 10 | const options = {}; 11 | const log = console.log; 12 | 13 | // Workaround for Node.js v6. https://twitter.com/izs/status/732317861067153408 14 | [process.stdout, process.stderr].forEach((s) => { 15 | s && s._handle && s._handle.setBlocking && s._handle.setBlocking(true); 16 | }); 17 | 18 | const showHelp = function() { 19 | log( 20 | 'regexpu v%s - https://mths.be/regexpu', 21 | regexpu.version 22 | ); 23 | log([ 24 | '\nUsage:\n', 25 | '\tregexpu [snippet ...]', 26 | '\tregexpu [-p | --unicode-property-escape] [-u | --use-unicode-flag] [snippet ...]', 27 | '\tregexpu [-f | --flags] [gimsuy] [-s | --dotall]', 28 | '\tregexpu [-v | --version]', 29 | '\tregexpu [-h | --help]', 30 | '\nExamples:\n', 31 | '\tregexpu \'const x = /foo.bar/u;\'', 32 | '\tregexpu -p -u \'const x = /\\p{Lu}/u;\'', 33 | '\techo \'const x = /foo.bar/u;\' | regexpu' 34 | ].join('\n')); 35 | }; 36 | 37 | const main = function() { 38 | 39 | if (!strings.length) { 40 | showHelp(); 41 | return process.exit(1); 42 | } 43 | 44 | for (const string of strings) { 45 | 46 | let snippet; 47 | 48 | if (/^(?:-h|--help|undefined)$/.test(string)) { 49 | showHelp(); 50 | return process.exit(1); 51 | } 52 | 53 | if (/^(?:-v|--version)$/.test(string)) { 54 | log('v%s', regexpu.version); 55 | return process.exit(1); 56 | } 57 | 58 | if (/^(?:-f|--flags)$/.test(string)) { 59 | isFlags = true; 60 | continue; 61 | } 62 | 63 | if (/^(?:-s|--dotall)$/.test(string)) { 64 | options.dotAllFlag = true; 65 | continue; 66 | } 67 | 68 | if (/^(?:-p|--unicode-property-escape)$/.test(string)) { 69 | options.unicodePropertyEscape = true; 70 | continue; 71 | } 72 | 73 | if (/^(?:-u|--use-unicode-flag)$/.test(string)) { 74 | options.useUnicodeFlag = true; 75 | continue; 76 | } 77 | 78 | if (isFlags) { 79 | flags = string; 80 | isFlags = false; 81 | continue; 82 | } 83 | 84 | snippet = string; 85 | try { 86 | const result = regexpu.transpileCode(snippet, options); 87 | log(result); 88 | } catch (exception) { 89 | log(exception.message + '\n'); 90 | log('Error: failed to transpile. Make sure the JavaScript code is valid.'); 91 | log('If you think this is a bug in regexpu, please report it:'); 92 | log('https://github.com/mathiasbynens/regexpu/issues/new'); 93 | log('\nStack trace using regexpu@%s:\n', regexpu.version); 94 | log(exception.stack); 95 | return process.exit(1); 96 | } 97 | 98 | } 99 | 100 | // Return with exit status 0 outside of the `forEach` loop, in case 101 | // multiple snippets or files were passed in. 102 | return process.exit(0); 103 | }; 104 | 105 | if (stdin.isTTY) { 106 | // handle shell arguments 107 | main(); 108 | } else { 109 | let timeout; 110 | // Either the script is called from within a non-TTY context, or `stdin` 111 | // content is being piped in. 112 | if (!process.stdout.isTTY) { 113 | // The script was called from a non-TTY context. This is a rather uncommon 114 | // use case we don’t actively support. However, we don’t want the script 115 | // to wait forever in such cases, so… 116 | timeout = setTimeout(function() { 117 | // …if no piped data arrived after a whole minute, handle shell 118 | // arguments instead. 119 | main(); 120 | }, 60000); 121 | } 122 | let data = ''; 123 | stdin.on('data', function(chunk) { 124 | clearTimeout(timeout); 125 | data += chunk; 126 | }); 127 | stdin.on('end', function() { 128 | strings.push(data.trim()); 129 | main(); 130 | }); 131 | stdin.resume(); 132 | } 133 | -------------------------------------------------------------------------------- /man/regexpu.1: -------------------------------------------------------------------------------- 1 | .Dd January 23, 2017 2 | .Dt regexpu 1 3 | .Sh NAME 4 | .Nm regexpu 5 | .Nd transpile ES2015 Unicode regular expressions to equivalent ES5 6 | .Sh SYNOPSIS 7 | .Nm 8 | .Op Fl -c | -code Ar snippet 9 | .br 10 | .Op Fl -f | -file Ar file 11 | .br 12 | .Op Fl v | -version 13 | .br 14 | .Op Fl h | -help 15 | .Sh DESCRIPTION 16 | .Nm 17 | transpiles ES2015 Unicode regular expressions to equivalent ES5. 18 | .Sh OPTIONS 19 | .Bl -ohang -offset 20 | .It Sy "-c, --code " 21 | Print a transpiled version of the given ES2015 snippet. 22 | .It Sy "-f, --file " 23 | Print a transpiled version of the given ES2015 file. 24 | .It Sy "-p, --unicode-property-escape" 25 | Enable support for Unicode property escapes. 26 | .It Sy "-u, --use-unicode-flag" 27 | Use Unicode code point escapes (which require the `u` flag) in the output. 28 | .It Sy "-v, --version" 29 | Print regexpu's version. 30 | .It Sy "-h, --help" 31 | Show the help screen. 32 | .El 33 | .Sh EXIT STATUS 34 | The 35 | .Nm regexpu 36 | utility exits with one of the following values: 37 | .Pp 38 | .Bl -tag -width flag -compact 39 | .It Li 0 40 | .Nm 41 | successfully transpiled the input and printed the result. 42 | .It Li 1 43 | .Nm 44 | wasn't instructed to transpile anything (for example, the 45 | .Ar --help 46 | flag was set); or, an error occurred. 47 | .El 48 | .Sh EXAMPLES 49 | .Bl -ohang -offset 50 | .It Sy "regexpu -f foo.js > foo-es5.js" 51 | Transpile `foo.js` to valid ES5 and save the result as `foo-es5.js`. 52 | .It Sy "regexpu -c 'var x = /foo.bar/u;'" 53 | Print the transpiled version of the given ES2015 snippet. 54 | .It Sy "echo 'var x = /foo.bar/u;' | regexpu -c" 55 | Print the transpiled version of the ES2015 snippet that gets piped in. 56 | .El 57 | .Sh BUGS 58 | regexpu's bug tracker is located at . 59 | .Sh AUTHOR 60 | Mathias Bynens 61 | .Sh WWW 62 | 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regexpu", 3 | "version": "5.1.0", 4 | "description": "A source code transpiler that enables the use of ES2015 Unicode regular expressions in ES5.", 5 | "homepage": "https://mths.be/regexpu", 6 | "main": "regexpu.js", 7 | "engines": { 8 | "node": ">=6" 9 | }, 10 | "bin": "bin/regexpu", 11 | "keywords": [ 12 | "codegen", 13 | "desugaring", 14 | "ecmascript", 15 | "es5", 16 | "es6", 17 | "es2015", 18 | "harmony", 19 | "javascript", 20 | "refactoring", 21 | "regex", 22 | "regexp", 23 | "regular expressions", 24 | "rewriting", 25 | "syntax", 26 | "transformation", 27 | "transpile", 28 | "transpiler", 29 | "unicode" 30 | ], 31 | "license": "MIT", 32 | "author": { 33 | "name": "Mathias Bynens", 34 | "url": "https://mathiasbynens.be/" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/mathiasbynens/regexpu.git" 39 | }, 40 | "bugs": "https://github.com/mathiasbynens/regexpu/issues", 41 | "files": [ 42 | "LICENSE-MIT.txt", 43 | "regexpu.js", 44 | "transform-tree.js", 45 | "transpile-code.js", 46 | "bin/", 47 | "man/" 48 | ], 49 | "directories": { 50 | "bin": "bin", 51 | "man": "man" 52 | }, 53 | "scripts": { 54 | "build": "scripts/build", 55 | "test": "mocha tests", 56 | "cover": "istanbul cover --report html node_modules/.bin/_mocha tests/tests.js -- -u exports -R spec" 57 | }, 58 | "dependencies": { 59 | "jsesc": "^3.0.2", 60 | "recast": "^0.23.9", 61 | "regexpu-core": "^6.0.0" 62 | }, 63 | "devDependencies": { 64 | "babili": "0.1.4", 65 | "browserify": "^17.0.0", 66 | "codecov": "^3.8.3", 67 | "istanbul": "^0.4.5", 68 | "lodash": "^4.17.21", 69 | "mocha": "^10.7.3", 70 | "regexpu-fixtures": "^2.1.6" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /regexpu.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'rewritePattern': require('regexpu-core'), 3 | 'transformTree': require('./transform-tree.js'), 4 | 'transpileCode': require('./transpile-code.js'), 5 | 'version': require('./package.json').version 6 | }; 7 | 8 | module.exports.transform = module.exports.transformTree; 9 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | 3 | args=$( 4 | for path in $(ls ./node_modules/regenerate-unicode-properties/*/*.js); do 5 | name=${path/.\/node_modules\//}; 6 | printf -- "--require ${path}:${name} "; 7 | done; 8 | ); 9 | 10 | mkdir -p dist; 11 | 12 | echo 'Building browser-compatible version…'; 13 | browserify $args --require ./regexpu.js:regexpu -o dist/regexpu-browser.js; 14 | 15 | echo 'Minifying…'; 16 | babili --no-comments dist/regexpu-browser.js > dist/regexpu-browser.min.js; 17 | 18 | echo 'Updating online demo…'; 19 | rsync --verbose --archive --compress \ 20 | dist/regexpu-browser.min.js \ 21 | mothereff.in:httpdocs/regexpu.min.js || true; 22 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const fixtures = require('regexpu-fixtures'); 5 | const regexpu = require('../regexpu.js'); 6 | 7 | describe('API', function() { 8 | it('supports loading each API method separately', function() { 9 | assert.equal(regexpu.rewritePattern, require('regexpu-core')); 10 | assert.equal(regexpu.transformTree, require('../transform-tree')); 11 | assert.equal(regexpu.transpileCode, require('../transpile-code')); 12 | }); 13 | }); 14 | 15 | describe('regexpu.rewritePattern', function() { 16 | 17 | for (const fixture of fixtures) { 18 | const pattern = fixture.pattern; 19 | for (const flag of fixture.flags) { 20 | it('rewrites `/' + pattern + '/' + flag + '` correctly', function() { 21 | assert.equal( 22 | regexpu.rewritePattern(pattern, flag), 23 | fixture.transpiled 24 | ); 25 | }); 26 | } 27 | } 28 | 29 | }); 30 | 31 | describe('regexpu.transformTree', function() { 32 | 33 | it('is aliased as `regexpu.transform`', function() { 34 | assert.equal(regexpu.transform, regexpu.transformTree); 35 | }); 36 | 37 | // Functional tests have been omitted here, because `transformTree` is 38 | // already tested indirectly through `transpileCode`. 39 | 40 | }); 41 | 42 | describe('regexpu.transpileCode', function() { 43 | 44 | for (const fixture of fixtures) { 45 | for (const flag of fixture.flags) { 46 | if (!flag.includes('u')) { 47 | // Unlike `rewritePattern` (which rewrites any regular expression you 48 | // feed it), the transpiler is only supposed to handle regular 49 | // expressions with the `u` flag set. This one doesn’t, so skip it. 50 | continue; 51 | } 52 | const code = `var x = /${ fixture.pattern }/${ flag };`; 53 | const expected = `var x = /${ fixture.transpiled }/${ 54 | flag.replace('u', '') };`; 55 | it('transpiles `' + code + '` correctly', function() { 56 | assert.equal(regexpu.transpileCode(code), expected); 57 | }); 58 | } 59 | } 60 | 61 | it('creates source maps on request', function() { 62 | const result = regexpu.transpileCode('var x = /[\\u{1D306}-\\u{1D308}]/u;', { 63 | 'sourceFileName': 'es2015.js', 64 | 'sourceMapName': 'es2015.map', 65 | }); 66 | assert.equal(result.code, 'var x = /(?:\\uD834[\\uDF06-\\uDF08])/;'); 67 | assert.deepEqual(result.map, { 68 | 'version': 3, 69 | 'file': 'es2015.map', 70 | 'sources': ['es2015.js'], 71 | 'names': [], 72 | 'mappings': 'AAAA,CAAC,CAAC,EAAE,EAAE,6BAA0B', 73 | 'sourcesContent': [ 74 | 'var x = /[\\u{1D306}-\\u{1D308}]/u;' 75 | ] 76 | }); 77 | }); 78 | 79 | it('doesn’t transpile anything else', function() { 80 | assert.equal(regexpu.transpileCode('var x = /a/;'), 'var x = /a/;'); 81 | assert.equal(regexpu.transpileCode('var x = 42;'), 'var x = 42;'); 82 | assert.equal(regexpu.transpileCode('var x = "abc";'), 'var x = "abc";'); 83 | assert.equal(regexpu.transpileCode('var x = "a/b/u";'), 'var x = "a/b/u";'); 84 | assert.equal(regexpu.transpileCode('var x = true;'), 'var x = true;'); 85 | assert.equal(regexpu.transpileCode('var x = false;'), 'var x = false;'); 86 | assert.equal(regexpu.transpileCode('var x = undefined;'), 'var x = undefined;'); 87 | assert.equal(regexpu.transpileCode('var x = null;'), 'var x = null;'); 88 | assert.equal(regexpu.transpileCode('var x = [];'), 'var x = [];'); 89 | assert.equal(regexpu.transpileCode('var x = {};'), 'var x = {};'); 90 | }); 91 | 92 | it('passes its `options` argument to `rewritePattern`', function() { 93 | assert.equal( 94 | regexpu.transpileCode('var x = /\\p{ASCII}/u;', { 95 | 'unicodePropertyEscape': true 96 | }), 97 | 'var x = /[\\0-\\x7F]/;' 98 | ); 99 | assert.equal( 100 | regexpu.transpileCode('var x = /\\p{Script_Extensions=Anatolian_Hieroglyphs}/u;', { 101 | 'unicodePropertyEscape': true 102 | }), 103 | 'var x = /(?:\\uD811[\\uDC00-\\uDE46])/;' 104 | ); 105 | assert.equal( 106 | regexpu.transpileCode('var x = /\\p{Script_Extensions=Anatolian_Hieroglyphs}/u;', { 107 | 'unicodePropertyEscape': true, 108 | 'useUnicodeFlag': true 109 | }), 110 | 'var x = /[\\u{14400}-\\u{14646}]/u;' 111 | ); 112 | assert.equal( 113 | regexpu.transpileCode('var x = /./s;', { 114 | 'dotAllFlag': true 115 | }), 116 | 'var x = /[\\s\\S]/;' 117 | ); 118 | assert.equal( 119 | regexpu.transpileCode('var x = /./u;', { 120 | 'dotAllFlag': true 121 | }), 122 | 'var x = /(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])/;' 123 | ); 124 | assert.equal( 125 | regexpu.transpileCode('var x = /./su;', { 126 | 'dotAllFlag': true 127 | }), 128 | 'var x = /(?:[\\0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])/;' 129 | ); 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /transform-tree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const recast = require('recast'); 4 | const rewritePattern = require('regexpu-core'); 5 | const types = recast.types; 6 | 7 | module.exports = function(node, rewritePatternOptions) { 8 | return types.visit(node, types.PathVisitor.fromMethodsObject({ 9 | // This method is called for any AST node whose `type` is `Literal`. 10 | 'visitLiteral': function(path) { 11 | const node = path.value; 12 | 13 | if (!node.regex) { 14 | return false; 15 | } 16 | 17 | const flags = node.regex.flags; 18 | const useDotAll = rewritePatternOptions.dotAllFlag === 'transform' && flags.includes('s'); 19 | if (!flags.includes('u') && !useDotAll) { 20 | return false; 21 | } 22 | 23 | const newPattern = rewritePattern( 24 | node.regex.pattern, 25 | flags, 26 | rewritePatternOptions 27 | ); 28 | const filteredFlags = useDotAll ? flags.replace('s', '') : flags; 29 | const newFlags = rewritePatternOptions.unicodeFlag === 'transform' ? 30 | filteredFlags : 31 | filteredFlags.replace('u', ''); 32 | const result = `/${ newPattern }/${ newFlags }`; 33 | node.regex = { 34 | 'pattern': newPattern, 35 | 'flags': newFlags 36 | }; 37 | node.raw = result; 38 | node.value = { 39 | 'toString': () => result 40 | }; 41 | 42 | // Return `false` to indicate that the traversal need not continue any 43 | // further down this subtree. (`Literal`s don’t have descendants anyway.) 44 | return false; 45 | } 46 | })); 47 | }; 48 | -------------------------------------------------------------------------------- /transpile-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const recast = require('recast'); 4 | const transform = require('./transform-tree.js'); 5 | 6 | module.exports = function(input, options) { 7 | options || (options = {}); 8 | const sourceFileName = options.sourceFileName || ''; 9 | const sourceMapName = options.sourceMapName || ''; 10 | const enableDotAllFlag = options.dotAllFlag || false; 11 | const enableUnicodePropertyEscapes = options.unicodePropertyEscape || false; 12 | const useUnicodeFlag = options.useUnicodeFlag || false; 13 | const createSourceMap = sourceFileName && sourceMapName; 14 | const tree = recast.parse(input, { 15 | 'sourceFileName': sourceFileName 16 | }); 17 | const transformed = transform(tree, { 18 | 'dotAllFlag': enableDotAllFlag ? 'transform' : false, 19 | 'unicodePropertyEscapes': enableUnicodePropertyEscapes ? 'transform' : false, 20 | 'unicodeFlag': useUnicodeFlag ? false : 'transform', 21 | }); 22 | if (createSourceMap) { 23 | // If a source map was requested, return an object with `code` and `map` 24 | // properties. 25 | return recast.print(transformed, { 26 | 'sourceMapName': sourceMapName 27 | }); 28 | } 29 | // If no source map was requested, return the transpiled code directly. 30 | return recast.print(transformed).code; 31 | }; 32 | --------------------------------------------------------------------------------