├── .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 [](https://travis-ci.org/mathiasbynens/regexpu) [](https://codecov.io/gh/mathiasbynens/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 | | [](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 |
--------------------------------------------------------------------------------