├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config-es5.js ├── config.js ├── examples ├── browser │ ├── index.css │ ├── index.html │ ├── index.js │ └── sample-config.js ├── replace-log.js └── sources │ └── log.js ├── package.json ├── redeyed.js └── test ├── redeyed-before-after-config.js ├── redeyed-comments.js ├── redeyed-config-with-undefineds.js ├── redeyed-function-config-extra-params.js ├── redeyed-function-config-skipping-tokens.js ├── redeyed-function-config.js ├── redeyed-incomplete.js ├── redeyed-jsx.js ├── redeyed-keywords.js ├── redeyed-mixed.js ├── redeyed-result.js ├── redeyed-script-level-return.js ├── redeyed-shebang.js ├── redeyed-smoke.js ├── redeyed-string-config.js ├── redeyed-types.js └── redeyed-upcoming.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "8" 6 | - "10" 7 | before_install: 8 | - npm install -g npm@2 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Thorsten Lorenz. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redeyed [![build status](https://secure.travis-ci.org/thlorenz/redeyed.svg?branch=master)](http://travis-ci.org/thlorenz/redeyed) 2 | 3 | become a patron 4 | 5 | *Add color to your JavaScript!* 6 | 7 | ![frog](http://allaboutfrogs.org/gallery/photos/redeyes/red1.gif) 8 | 9 | [Red Eyed Tree Frog](http://allaboutfrogs.org/info/species/redeye.html) *(Agalychnis callidryas)* 10 | 11 | ## What? 12 | 13 | Takes JavaScript code, along with a config and returns the original code with tokens wrapped and/or replaced as configured. 14 | 15 | ## Where? 16 | 17 | - server side using nodejs 18 | - in the [browser](#browser-support) 19 | 20 | ## What for? 21 | 22 | One usecase is adding metadata to your code that can then be used to apply syntax highlighting. 23 | 24 | ## How? 25 | 26 | - copy the [config.js](https://github.com/thlorenz/redeyed/blob/master/config.js) and edit it in order to specify how 27 | certain tokens are to be surrounded/replaced 28 | - replace the `undefined` of each token you want to configure with one of the following 29 | 30 | ### {String} config 31 | 32 | `'before:after'` 33 | 34 | wraps the token inside before/after 35 | 36 | ### {Object} config 37 | 38 | `{ _before: 'before', _after: 'after' }` 39 | 40 | wraps token inside before/after 41 | 42 | #### Missing before and after resolution for {String} and {Object} config 43 | 44 | For the `{String}` and `{Object}` configurations, 'before' or 'after' may be omitted: 45 | 46 | - `{String}`: 47 | - `'before:'` (omitting 'after') 48 | - `':after'` (omitting 'before') 49 | - `{Object}`: 50 | - `{ _before: 'before' }` (omitting '_after') 51 | - `{ _after: 'after' }` (omitting '_before') 52 | 53 | In these cases the missing half is resolved as follows: 54 | 55 | - from the `parent._default` (i.e., `Keyword._default`) if found 56 | - otherwise from the `config._default` if found 57 | - otherwise `''` (empty string) 58 | 59 | ### {Function} config 60 | 61 | `function (tokenString, info) { return {String}|{Object}; }` 62 | 63 | #### Inputs 64 | 65 | - tokenString: the content of the token that is currently being processed 66 | - info: an object with the following structure 67 | 68 | ```js 69 | { 70 | // {Int} 71 | // the index of the token being processed inside tokens 72 | tokenIndex 73 | 74 | // {Array} 75 | // all tokens that are being processed including comments 76 | // (i.e. the result of merging esprima tokens and comments) 77 | , tokens 78 | 79 | // {Object} 80 | // the abstract syntax tree of the parsed code 81 | , ast 82 | 83 | // {String} 84 | // the code that was parsed (same string as the one passed to redeyed(code ..) 85 | , code 86 | } 87 | ``` 88 | 89 | In most cases the `tokenString` is all you need. The extra info object is passed in case you need to gather more 90 | information about the `token`'s surroundings in order to decide how to transform it. 91 | See: [replace-log-example](https://github.com/thlorenz/redeyed/blob/master/examples/replace-log.js) 92 | 93 | #### Output 94 | 95 | You can return a {String} or an {Object} from a {Function} config. 96 | 97 | - when returning a {String}, the token value will be replaced with it 98 | - when returning an {Object}, it should be of the following form: 99 | 100 | ```js 101 | { 102 | // {String} 103 | // the string that should be substituted for the value of the current and all skipped tokens 104 | replacement 105 | 106 | // {Object} (Token) 107 | // the token after which processing should continue 108 | // all tokens in between the current one and this one inclusive will be ignored 109 | , skipPastToken 110 | } 111 | ``` 112 | 113 | ### Transforming JavaScript code 114 | 115 | ***redeyed(code, config[, opts])*** 116 | 117 | Invoke redeyed with your **config**uration, a **code** snippet and maybe **opts** as in the below example: 118 | 119 | ```javascript 120 | var redeyed = require('redeyed') 121 | , config = require('./path/to/config') 122 | , code = 'var a = 3;' 123 | , result; 124 | 125 | // redeyed will throw an error (caused by the esprima parser) if the code has invalid javascript 126 | try { 127 | result = redeyed(code, config); 128 | console.log(result.code); 129 | } catch(err) { 130 | console.error(err); 131 | } 132 | ``` 133 | 134 | ***opts***: 135 | ```js 136 | { // {Boolean} 137 | // if true `result.ast` property contains the abstract syntax tree of the code 138 | // if false (default) `result.ast` is not assigned and therefore `undefined` 139 | buildAst: true|false 140 | // {Boolean} 141 | // if `true`, jsx syntax is supported, default `false` 142 | // due to how esprima works, the AST is built when this option is `true`, even if 143 | // `buildAST` is `false` 144 | , jsx: true|false 145 | // {Boolean} 146 | // if true `result.code` is not assigned and therefore `undefined` 147 | // if false (default) `result.code` property contains the result of `split.join` 148 | nojoin: true|false 149 | // {Object} 150 | // overrides default parser `esprima-fb` and needs to be compatible with it 151 | parser: require('esprima') 152 | } 153 | ``` 154 | 155 | ***return value***: 156 | 157 | ```js 158 | { ast 159 | , tokens 160 | , comments 161 | , splits 162 | , code 163 | } 164 | ``` 165 | 166 | - ast `{Array}`: [abstract syntax tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) as returned by [esprima 167 | parse](http://en.wikipedia.org/wiki/Abstract_syntax_tree) 168 | - tokens `{Array}`: [tokens](http://en.wikipedia.org/wiki/Token_(parser)) provided by esprima (excluding 169 | comments) 170 | - comments `{Array}`: block and line comments as provided by esprima 171 | - splits `{Array}`: code pieces split up, some of which where transformed as configured 172 | - code `{String}`: transformed code, same as `splits.join('')` unless this step has been skipped (see opts) 173 | 174 | ## Browser Support 175 | 176 | ### AMD 177 | 178 | Ensure to include [esprima](https://github.com/ariya/esprima) as one of your dependencies 179 | 180 | ```js 181 | define(['redeyed'], function (redeyed) { 182 | [ .. ] 183 | }); 184 | ``` 185 | 186 | ### Attached to global window object 187 | 188 | The `redeyed {Function}` will be exposed globally as `window.redeyed` - big surprise! 189 | 190 | ```html 191 | 192 | 193 | ``` 194 | 195 | ## redeyed in the wild 196 | 197 | - [cardinal](https://github.com/thlorenz/cardinal): Syntax highlights JavaScript code with ANSI colors to be printed to 198 | the terminal 199 | - [peacock](http://thlorenz.github.com/peacock/): JavaScript syntax highlighter that generates html that is compatible 200 | with pygments styles. 201 | 202 | ## Examples 203 | 204 | - `npm explore redeyed; npm demo` will let you try the [browser example](https://github.com/thlorenz/redeyed/tree/master/examples/browser) 205 | - `npm explore redeyed; npm demo-log` will let you try the [replace log example](https://github.com/thlorenz/redeyed/blob/master/examples/replace-log.js) 206 | 207 | -------------------------------------------------------------------------------- /config-es5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This config only contains ES5 tokens while the default config.js contains 3 | * ES6 tokens as well. 4 | * 5 | * Copy this file and use it as a starting point for your redeyed config. 6 | * Just fill in the tokens you want to surround/replace. 7 | * Keep in mind that more specific configurations override less specific ones. 8 | */ 9 | 10 | module.exports = { 11 | 12 | 'Boolean': { 13 | 'true' : undefined 14 | , 'false' : undefined 15 | , _default : undefined 16 | } 17 | 18 | , 'Identifier': { 19 | _default: undefined 20 | } 21 | 22 | , 'Null': { 23 | _default: undefined 24 | } 25 | 26 | , 'Numeric': { 27 | _default: undefined 28 | } 29 | 30 | , 'String': { 31 | _default: undefined 32 | } 33 | 34 | , 'Keyword': { 35 | 'break' : undefined 36 | 37 | , 'case' : undefined 38 | , 'catch' : undefined 39 | , 'continue' : undefined 40 | 41 | , 'debugger' : undefined 42 | , 'default' : undefined 43 | , 'delete' : undefined 44 | , 'do' : undefined 45 | 46 | , 'else' : undefined 47 | 48 | , 'finally' : undefined 49 | , 'for' : undefined 50 | , 'function' : undefined 51 | 52 | , 'if' : undefined 53 | , 'in' : undefined 54 | , 'instanceof' : undefined 55 | 56 | , 'new' : undefined 57 | , 'return' : undefined 58 | , 'switch' : undefined 59 | 60 | , 'this' : undefined 61 | , 'throw' : undefined 62 | , 'try' : undefined 63 | , 'typeof' : undefined 64 | 65 | , 'var' : undefined 66 | , 'void' : undefined 67 | 68 | , 'while' : undefined 69 | , 'with' : undefined 70 | , _default : undefined 71 | } 72 | , 'Punctuator': { 73 | ';': undefined 74 | , '.': undefined 75 | , ',': undefined 76 | 77 | , '{': undefined 78 | , '}': undefined 79 | , '(': undefined 80 | , ')': undefined 81 | , '[': undefined 82 | , ']': undefined 83 | 84 | , '<': undefined 85 | , '>': undefined 86 | , '+': undefined 87 | , '-': undefined 88 | , '*': undefined 89 | , '%': undefined 90 | , '&': undefined 91 | , '|': undefined 92 | , '^': undefined 93 | , '!': undefined 94 | , '~': undefined 95 | , '?': undefined 96 | , ':': undefined 97 | , '=': undefined 98 | 99 | , '<=': undefined 100 | , '>=': undefined 101 | , '==': undefined 102 | , '!=': undefined 103 | , '++': undefined 104 | , '--': undefined 105 | , '<<': undefined 106 | , '>>': undefined 107 | , '&&': undefined 108 | , '||': undefined 109 | , '+=': undefined 110 | , '-=': undefined 111 | , '*=': undefined 112 | , '%=': undefined 113 | , '&=': undefined 114 | , '|=': undefined 115 | , '^=': undefined 116 | , '/=': undefined 117 | 118 | , '===': undefined 119 | , '!==': undefined 120 | , '>>>': undefined 121 | , '<<=': undefined 122 | , '>>=': undefined 123 | 124 | , '>>>=': undefined 125 | 126 | , _default: undefined 127 | } 128 | 129 | // line comment 130 | , Line: { 131 | _default: undefined 132 | } 133 | 134 | /* block comment */ 135 | , Block: { 136 | _default: undefined 137 | } 138 | 139 | , _default: undefined 140 | } 141 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy this file and use it as a starting point for your redeyed config. 3 | * Just fill in the tokens you want to surround/replace. 4 | * Keep in mind that more specific configurations override less specific ones. 5 | */ 6 | 7 | module.exports = { 8 | 9 | 'Boolean': { 10 | 'true' : undefined 11 | , 'false' : undefined 12 | , _default : undefined 13 | } 14 | 15 | , 'Identifier': { 16 | _default: undefined 17 | } 18 | 19 | , 'Null': { 20 | _default: undefined 21 | } 22 | 23 | , 'Numeric': { 24 | _default: undefined 25 | } 26 | 27 | , 'String': { 28 | _default: undefined 29 | } 30 | 31 | , 'Keyword': { 32 | 'break' : undefined 33 | 34 | , 'case' : undefined 35 | , 'catch' : undefined 36 | , 'class' : undefined 37 | , 'const' : undefined 38 | , 'continue' : undefined 39 | 40 | , 'debugger' : undefined 41 | , 'default' : undefined 42 | , 'delete' : undefined 43 | , 'do' : undefined 44 | 45 | , 'else' : undefined 46 | , 'enum' : undefined 47 | , 'export' : undefined 48 | , 'extends' : undefined 49 | 50 | , 'finally' : undefined 51 | , 'for' : undefined 52 | , 'function' : undefined 53 | 54 | , 'if' : undefined 55 | , 'implements' : undefined 56 | , 'import' : undefined 57 | , 'in' : undefined 58 | , 'instanceof' : undefined 59 | , 'interface' : undefined 60 | , 'let' : undefined 61 | , 'new' : undefined 62 | 63 | , 'package' : undefined 64 | , 'private' : undefined 65 | , 'protected' : undefined 66 | , 'public' : undefined 67 | 68 | , 'return' : undefined 69 | , 'static' : undefined 70 | , 'super' : undefined 71 | , 'switch' : undefined 72 | 73 | , 'this' : undefined 74 | , 'throw' : undefined 75 | , 'try' : undefined 76 | , 'typeof' : undefined 77 | 78 | , 'var' : undefined 79 | , 'void' : undefined 80 | 81 | , 'while' : undefined 82 | , 'with' : undefined 83 | , 'yield' : undefined 84 | , _default : undefined 85 | } 86 | , 'Punctuator': { 87 | ';': undefined 88 | , '.': undefined 89 | , ',': undefined 90 | 91 | , '{': undefined 92 | , '}': undefined 93 | , '(': undefined 94 | , ')': undefined 95 | , '[': undefined 96 | , ']': undefined 97 | 98 | , '<': undefined 99 | , '>': undefined 100 | , '+': undefined 101 | , '-': undefined 102 | , '*': undefined 103 | , '%': undefined 104 | , '&': undefined 105 | , '|': undefined 106 | , '^': undefined 107 | , '!': undefined 108 | , '~': undefined 109 | , '?': undefined 110 | , ':': undefined 111 | , '=': undefined 112 | 113 | , '<=': undefined 114 | , '>=': undefined 115 | , '==': undefined 116 | , '!=': undefined 117 | , '++': undefined 118 | , '--': undefined 119 | , '<<': undefined 120 | , '>>': undefined 121 | , '&&': undefined 122 | , '||': undefined 123 | , '+=': undefined 124 | , '-=': undefined 125 | , '*=': undefined 126 | , '%=': undefined 127 | , '&=': undefined 128 | , '|=': undefined 129 | , '^=': undefined 130 | , '/=': undefined 131 | , '=>': undefined 132 | , '**': undefined 133 | 134 | , '===': undefined 135 | , '!==': undefined 136 | , '>>>': undefined 137 | , '<<=': undefined 138 | , '>>=': undefined 139 | , '...': undefined 140 | , '**=': undefined 141 | 142 | , '>>>=': undefined 143 | 144 | , _default: undefined 145 | } 146 | 147 | // line comment 148 | , Line: { 149 | _default: undefined 150 | } 151 | 152 | /* block comment */ 153 | , Block: { 154 | _default: undefined 155 | } 156 | 157 | // JSX 158 | , JSXAttribute: { 159 | _default: undefined 160 | } 161 | , JSXClosingElement: { 162 | _default: undefined 163 | } 164 | , JSXElement: { 165 | _default: undefined 166 | } 167 | , JSXEmptyExpression: { 168 | _default: undefined 169 | } 170 | , JSXExpressionContainer: { 171 | _default: undefined 172 | } 173 | , JSXIdentifier: { 174 | // many more identifies are possible, div, table, etc. 175 | className: undefined 176 | , _default: undefined 177 | } 178 | , JSXMemberExpression: { 179 | _default: undefined 180 | } 181 | , JSXNamespacedName: { 182 | _default: undefined 183 | } 184 | , JSXOpeningElement: { 185 | _default: undefined 186 | } 187 | , JSXSpreadAttribute: { 188 | _default: undefined 189 | } 190 | , JSXText: { 191 | _default: undefined 192 | } 193 | 194 | , _default: undefined 195 | } 196 | -------------------------------------------------------------------------------- /examples/browser/index.css: -------------------------------------------------------------------------------- 1 | .code { 2 | width: 500px; 3 | height: 500px; 4 | display: block; 5 | } 6 | 7 | .config { 8 | width: 500px; 9 | height: 400px; 10 | display: block; 11 | } 12 | 13 | .go { 14 | display: block; 15 | float: right; 16 | font-size: 18px; 17 | } 18 | 19 | .result { 20 | width: 500px; 21 | height: 1000px; 22 | display: block; 23 | } 24 | 25 | .edit { 26 | float: left; 27 | } 28 | 29 | .results { 30 | float: left; 31 | } 32 | -------------------------------------------------------------------------------- /examples/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | Change the config and/or the original code in order to affect the "redeyed" result 11 |
12 |
13 |
14 |

Redeyed Config

15 | 16 | 17 |
18 |
19 |

Original Code

20 | 21 |
22 |
23 |
24 |

Result

25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/browser/index.js: -------------------------------------------------------------------------------- 1 | /* global $ redeyed */ 2 | var $code = $('.code') 3 | var $config = $('.config') 4 | var $result = $('.result') 5 | 6 | function go() { 7 | var config 8 | try { 9 | config = JSON.parse($config.val()) 10 | } catch (e) { 11 | $result.val('In "Redeyed Config": ' + e.toString()) 12 | return 13 | } 14 | 15 | try { 16 | var code = $code.val() 17 | var result = redeyed(code, config) 18 | 19 | $result.val(result.code) 20 | } catch (e) { 21 | $result.val('In "Original Code": ' + e.toString()) 22 | } 23 | } 24 | 25 | $code.val(window.redeyed.toString()) 26 | 27 | $config.val(JSON.stringify(window.sampleConfig, false, 2)) 28 | 29 | $('.go').click(go) 30 | 31 | go() 32 | 33 | -------------------------------------------------------------------------------- /examples/browser/sample-config.js: -------------------------------------------------------------------------------- 1 | window.sampleConfig = { 2 | 3 | 'Boolean': { 4 | 'true' : undefined 5 | , 'false' : undefined 6 | , _default : '?:?' 7 | } 8 | 9 | , 'Identifier': { 10 | _default: '-> : <-' 11 | } 12 | 13 | , 'Null': { 14 | _default: '**:**' 15 | } 16 | 17 | , 'Numeric': { 18 | _default: 'n:N' 19 | } 20 | 21 | , 'String': { 22 | _default: 'string -> :' 23 | } 24 | 25 | , 'Keyword': { 26 | 'break' : undefined 27 | 28 | , 'case' : undefined 29 | , 'catch' : undefined 30 | , 'continue' : undefined 31 | 32 | , 'debugger' : undefined 33 | , 'default' : undefined 34 | , 'delete' : undefined 35 | , 'do' : undefined 36 | 37 | , 'else' : undefined 38 | 39 | , 'finally' : undefined 40 | , 'for' : undefined 41 | , 'function' : undefined 42 | 43 | , 'if' : undefined 44 | , 'in' : undefined 45 | , 'instanceof' : undefined 46 | 47 | , 'new' : undefined 48 | , 'return' : undefined 49 | , 'switch' : undefined 50 | 51 | , 'this' : undefined 52 | , 'throw' : undefined 53 | , 'try' : undefined 54 | , 'typeof' : undefined 55 | 56 | , 'var' : undefined 57 | , 'void' : undefined 58 | 59 | , 'while' : undefined 60 | , 'with' : undefined 61 | , _default : ': <- keyword' 62 | } 63 | , 'Punctuator': { 64 | ';': undefined 65 | , '.': undefined 66 | , ',': undefined 67 | 68 | , '{': undefined 69 | , '}': undefined 70 | , '(': undefined 71 | , ')': undefined 72 | , '[': undefined 73 | , ']': undefined 74 | 75 | , '<': undefined 76 | , '>': undefined 77 | , '+': undefined 78 | , '-': undefined 79 | , '*': undefined 80 | , '%': undefined 81 | , '&': undefined 82 | , '|': undefined 83 | , '^': undefined 84 | , '!': undefined 85 | , '~': undefined 86 | , '?': undefined 87 | , ':': undefined 88 | , '=': undefined 89 | 90 | , '<=': undefined 91 | , '>=': undefined 92 | , '==': undefined 93 | , '!=': undefined 94 | , '++': undefined 95 | , '--': undefined 96 | , '<<': undefined 97 | , '>>': undefined 98 | , '&&': undefined 99 | , '||': undefined 100 | , '+=': undefined 101 | , '-=': undefined 102 | , '*=': undefined 103 | , '%=': undefined 104 | , '&=': undefined 105 | , '|=': undefined 106 | , '^=': undefined 107 | , '/=': undefined 108 | 109 | , '===': undefined 110 | , '!==': undefined 111 | , '>>>': undefined 112 | , '<<=': undefined 113 | , '>>=': undefined 114 | 115 | , '>>>=': undefined 116 | 117 | , _default: undefined 118 | } 119 | 120 | // line comment 121 | , Line: { 122 | _default: undefined 123 | } 124 | 125 | /* block comment */ 126 | , Block: { 127 | _default: undefined 128 | } 129 | 130 | , _default: undefined 131 | } 132 | -------------------------------------------------------------------------------- /examples/replace-log.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var redeyed = require('..') 4 | var vm = require('vm') 5 | 6 | var samplePath = path.join(__dirname, 'sources', 'log.js') 7 | var origCode = fs.readFileSync(samplePath, 'utf-8') 8 | var kinds = [ 'silly', 'info', 'warn', 'error' ] 9 | 10 | function replaceConsole(s, info) { 11 | var code = info.code 12 | var idx = info.tokenIndex 13 | var tokens = info.tokens 14 | var next = tokens[idx + 1].value 15 | var kind = tokens[idx + 2].value 16 | var openParen = tokens[idx + 3].value 17 | var firstArgTkn = tokens[idx + 4] 18 | var argIdx = idx + 3 19 | var open 20 | var tkn 21 | 22 | if (kind === 'log') kind = 'silly' 23 | 24 | // not a console.xxx(...) statement? -> just return original 25 | if (next !== '.' || !~kinds.indexOf(kind) || openParen !== '(') return s 26 | 27 | // skip past arguments to console.xxx all args from ( to ) 28 | open = 1 29 | while (open) { 30 | tkn = tokens[++argIdx] 31 | 32 | // count open parens vs. closed ones to handle things like console.log(new Error('..')); 33 | if (tkn.value === '(') open++ 34 | if (tkn.value === ')') open-- 35 | } 36 | 37 | // tkn now is the last closing paren 38 | var argsIncludingClosingParen = code.slice(firstArgTkn.range[0], tkn.range[1]) 39 | var result = 'log.' + kind + '("main-logger", ' + argsIncludingClosingParen 40 | 41 | // tell redeyed to skip the entire console.xxx(..) statement since we are replacing it all 42 | return { replacement: result, skipPastToken: tkn } 43 | } 44 | 45 | function transformAndRun() { 46 | var config = { 47 | Identifier: { console: replaceConsole } 48 | } 49 | var code = redeyed(origCode, config).code 50 | var context = vm.createContext({ require: require }) 51 | 52 | console.log('Original code:\n', origCode) 53 | console.log('\nlog calls replaced:\n', code) 54 | console.log('\nLets run it:') 55 | vm.runInContext(code, context, 'transformed-log.vm') 56 | } 57 | 58 | transformAndRun() 59 | -------------------------------------------------------------------------------- /examples/sources/log.js: -------------------------------------------------------------------------------- 1 | // First two lines will be needed when we replaced all console.xxx statements with log.xxx 2 | var log = require('npmlog') 3 | log.level = 'silly' 4 | 5 | console.info('info ', 1) 6 | console.log('log ', 2) 7 | console.warn('warn ', 3) 8 | console.error('error ', new Error('oh my!')) 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redeyed", 3 | "version": "2.1.1", 4 | "description": "Takes JavaScript code, along with a config and returns the original code with tokens wrapped as configured.", 5 | "author": "Thorsten Lorenz (thlorenz.com)", 6 | "main": "redeyed.js", 7 | "scripts": { 8 | "test": "npm run run-test && npm run lint", 9 | "run-test": "tape test/*.js", 10 | "lint": "standart", 11 | "demo-log": "node examples/replace-log", 12 | "demo": "cd examples/browser; open index.html" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/thlorenz/redeyed.git" 17 | }, 18 | "files": [ 19 | "redeyed.js" 20 | ], 21 | "keywords": [ 22 | "ast", 23 | "syntax", 24 | "tree", 25 | "source", 26 | "wrap", 27 | "metadata" 28 | ], 29 | "license": "MIT", 30 | "devDependencies": { 31 | "cardinal": "~1.0.0", 32 | "readdirp": "~2.1.0", 33 | "standart": "^6.1.0", 34 | "tape": "~4.9.0" 35 | }, 36 | "dependencies": { 37 | "esprima": "~4.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /redeyed.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | 'use strict' 3 | /* global define */ 4 | 5 | var esprima 6 | var exportFn 7 | var toString = Object.prototype.toString 8 | 9 | if (typeof module === 'object' && typeof module.exports === 'object' && typeof require === 'function') { 10 | // server side 11 | esprima = require('esprima') 12 | exportFn = function(redeyed) { module.exports = redeyed } 13 | bootstrap(esprima, exportFn) 14 | } else if (typeof define === 'function' && define.amd) { 15 | // client side 16 | // amd 17 | define(['esprima'], function(esprima) { 18 | return bootstrap(esprima) 19 | }) 20 | } else if (typeof window === 'object') { 21 | // no amd -> attach to window if it exists 22 | // Note that this requires 'esprima' to be defined on the window, so that script has to be loaded first 23 | window.redeyed = bootstrap(window.esprima) 24 | } 25 | 26 | function bootstrap(esprima, exportFn) { 27 | function isFunction(obj) { 28 | return toString.call(obj) === '[object Function]' 29 | } 30 | 31 | function isString(obj) { 32 | return toString.call(obj) === '[object String]' 33 | } 34 | 35 | function isObject(obj) { 36 | return toString.call(obj) === '[object Object]' 37 | } 38 | 39 | function surroundWith(before, after) { 40 | return function(s) { return before + s + after } 41 | } 42 | 43 | function isNonCircular(key) { 44 | return key !== '_parent' 45 | } 46 | 47 | function objectizeString(value) { 48 | var vals = value.split(':') 49 | 50 | if (vals.length === 0 || vals.length > 2) { 51 | throw new Error( 52 | 'illegal string config: ' + value + 53 | '\nShould be of format "before:after"' 54 | ) 55 | } 56 | 57 | if (vals.length === 1 || vals[1].length === 0) { 58 | return vals.indexOf(':') < 0 ? { _before: vals[0] } : { _after: vals[0] } 59 | } else { 60 | return { _before: vals[0], _after: vals[1] } 61 | } 62 | } 63 | 64 | function objectize(node) { 65 | // Converts 'bef:aft' to { _before: bef, _after: aft } 66 | // and resolves undefined before/after from parent or root 67 | 68 | function resolve(value, key) { 69 | // resolve before/after from root or parent if it isn't present on the current node 70 | if (!value._parent) return undefined 71 | 72 | // Immediate parent 73 | if (value._parent._default && value._parent._default[key]) return value._parent._default[key] 74 | 75 | // Root 76 | var root = value._parent._parent 77 | if (!root) return undefined 78 | 79 | return root._default ? root._default[key] : undefined 80 | } 81 | 82 | function process(key) { 83 | var value = node[key] 84 | 85 | if (!value) return 86 | if (isFunction(value)) return 87 | 88 | // normalize all strings to objects 89 | if (isString(value)) { 90 | node[key] = value = objectizeString(value) 91 | } 92 | 93 | value._parent = node 94 | if (isObject(value)) { 95 | if (!value._before && !value._after) return objectize(value) 96 | 97 | // resolve missing _before or _after from parent(s) 98 | // in case we only have either one on this node 99 | value._before = value._before || resolve(value, '_before') 100 | value._after = value._after || resolve(value, '_after') 101 | 102 | return 103 | } 104 | 105 | throw new Error('nodes need to be either {String}, {Object} or {Function}.' + value + ' is neither.') 106 | } 107 | 108 | // Process _default ones first so children can resolve missing before/after from them 109 | if (node._default) process('_default') 110 | 111 | Object.keys(node) 112 | .filter(function(key) { 113 | return isNonCircular(key) 114 | && node.hasOwnProperty(key) 115 | && key !== '_before' 116 | && key !== '_after' 117 | && key !== '_default' 118 | }) 119 | .forEach(process) 120 | } 121 | 122 | function functionize(node) { 123 | Object.keys(node) 124 | .filter(function(key) { 125 | return isNonCircular(key) && node.hasOwnProperty(key) 126 | }) 127 | .forEach(function(key) { 128 | var value = node[key] 129 | 130 | if (isFunction(value)) return 131 | 132 | if (isObject(value)) { 133 | if (!value._before && !value._after) return functionize(value) 134 | 135 | // at this point before/after were "inherited" from the parent or root 136 | // (see objectize) 137 | var before = value._before || '' 138 | var after = value._after || '' 139 | 140 | node[key] = surroundWith(before, after) 141 | return node[key] 142 | } 143 | }) 144 | } 145 | 146 | function normalize(root) { 147 | objectize(root) 148 | functionize(root) 149 | } 150 | 151 | function mergeTokensAndComments(tokens, comments) { 152 | var all = {} 153 | 154 | function addToAllByRangeStart(t) { all[ t.range[0] ] = t } 155 | 156 | tokens.forEach(addToAllByRangeStart) 157 | comments.forEach(addToAllByRangeStart) 158 | 159 | // keys are sorted automatically 160 | return Object.keys(all) 161 | .map(function(k) { return all[k] }) 162 | } 163 | 164 | function redeyed(code, config, opts) { 165 | opts = opts || {} 166 | var parser = opts.parser || esprima 167 | var jsx = !!opts.jsx 168 | // tokenizer doesn't support JSX at this point (esprima@4.0.0) 169 | // therefore we need to generate the AST via the parser not only to 170 | // avoid the tokenizer from erroring but also to get JSXIdentifier tokens 171 | var buildAst = jsx || !!opts.buildAst 172 | 173 | var hashbang = '' 174 | var ast 175 | var tokens 176 | var comments 177 | var lastSplitEnd = 0 178 | var splits = [] 179 | var transformedCode 180 | var all 181 | var info 182 | 183 | // Replace hashbang line with empty whitespaces to preserve token locations 184 | if (code[0] === '#' && code[1] === '!') { 185 | hashbang = code.substr(0, code.indexOf('\n') + 1) 186 | code = Array.apply(0, Array(hashbang.length)).join(' ') + '\n' + code.substr(hashbang.length) 187 | } 188 | 189 | if (buildAst) { 190 | ast = parser.parse(code, { tokens: true, comment: true, range: true, loc: true, tolerant: true, jsx: true }) 191 | tokens = ast.tokens 192 | comments = ast.comments 193 | } else { 194 | tokens = [] 195 | comments = [] 196 | parser.tokenize(code, { range: true, loc: true, comment: true }, function(token) { 197 | if (token.type === 'LineComment') { 198 | token.type = 'Line' 199 | comments.push(token) 200 | } else if (token.type === 'BlockComment') { 201 | token.type = 'Block' 202 | comments.push(token) 203 | } else { 204 | // Optimistically upgrade 'static' to a keyword 205 | if (token.type === 'Identifier' && token.value === 'static') token.type = 'Keyword' 206 | tokens.push(token) 207 | } 208 | }) 209 | } 210 | normalize(config) 211 | 212 | function tokenIndex(tokens, tkn, start) { 213 | var current 214 | var rangeStart = tkn.range[0] 215 | 216 | for (current = start; current < tokens.length; current++) { 217 | if (tokens[current].range[0] === rangeStart) return current 218 | } 219 | 220 | throw new Error('Token %s not found at or after index: %d', tkn, start) 221 | } 222 | 223 | function process(surround) { 224 | var result 225 | var currentIndex 226 | var nextIndex 227 | var skip = 0 228 | var splitEnd 229 | 230 | result = surround(code.slice(start, end), info) 231 | if (isObject(result)) { 232 | splits.push(result.replacement) 233 | 234 | currentIndex = info.tokenIndex 235 | nextIndex = tokenIndex(info.tokens, result.skipPastToken, currentIndex) 236 | skip = nextIndex - currentIndex 237 | splitEnd = skip > 0 ? tokens[nextIndex - 1].range[1] : end 238 | } else { 239 | splits.push(result) 240 | splitEnd = end 241 | } 242 | 243 | return { skip: skip, splitEnd: splitEnd } 244 | } 245 | 246 | function addSplit(start, end, surround, info) { 247 | var result 248 | var skip = 0 249 | 250 | if (start >= end) return 251 | if (surround) { 252 | result = process(surround) 253 | skip = result.skip 254 | lastSplitEnd = result.splitEnd 255 | } else { 256 | splits.push(code.slice(start, end)) 257 | lastSplitEnd = end 258 | } 259 | 260 | return skip 261 | } 262 | 263 | all = mergeTokensAndComments(tokens, comments) 264 | for (var tokenIdx = 0; tokenIdx < all.length; tokenIdx++) { 265 | var token = all[tokenIdx] 266 | var surroundForType = config[token.type] 267 | var surround 268 | var start 269 | var end 270 | 271 | // At least the type (e.g., 'Keyword') needs to be specified for the token to be surrounded 272 | if (surroundForType) { 273 | // root defaults are only taken into account while resolving before/after otherwise 274 | // a root default would apply to everything, even if no type default was specified 275 | surround = surroundForType 276 | && surroundForType.hasOwnProperty(token.value) 277 | && surroundForType[token.value] 278 | && isFunction(surroundForType[token.value]) 279 | ? surroundForType[token.value] 280 | : surroundForType._default 281 | 282 | start = token.range[0] 283 | end = token.range[1] 284 | 285 | addSplit(lastSplitEnd, start) 286 | info = { tokenIndex: tokenIdx, tokens: all, ast: ast, code: code } 287 | tokenIdx += addSplit(start, end, surround, info) 288 | } 289 | } 290 | 291 | if (lastSplitEnd < code.length) { 292 | addSplit(lastSplitEnd, code.length) 293 | } 294 | 295 | if (!opts.nojoin) { 296 | transformedCode = splits.join('') 297 | if (hashbang.length > 0) { 298 | transformedCode = hashbang + transformedCode.substr(hashbang.length) 299 | } 300 | } 301 | 302 | return { 303 | ast : ast 304 | , tokens : tokens 305 | , comments : comments 306 | , splits : splits 307 | , code : transformedCode 308 | } 309 | } 310 | 311 | return exportFn ? exportFn(redeyed) : redeyed 312 | } 313 | })() 314 | -------------------------------------------------------------------------------- /test/redeyed-before-after-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var result = redeyed(code, opts).code 14 | this.equals(result, expected, inspect(code) + ' => ' + inspect(expected)) 15 | return this 16 | } 17 | 18 | t.end() 19 | }) 20 | test('\nbefore/after config, keywords', function(t) { 21 | var opts001 = { Keyword: { _default: { _before: '*', _after: '&' } } } 22 | t.test('\n# ' + inspect(opts001), function(t) { 23 | t.assertSurrounds('this', opts001, '*this&') 24 | t.assertSurrounds('if (a == 1) return', opts001, '*if& (a == 1) *return&') 25 | t.assertSurrounds('var n = new Test();', opts001, '*var& n = *new& Test();') 26 | t.end() 27 | }) 28 | 29 | var opts002 = { 30 | Keyword: { 31 | 'function': { _before: '^' } 32 | , 'return': { _before: '(', _after: ')' } 33 | , _default: { _before: '*', _after: '&' } 34 | } 35 | } 36 | 37 | t.test('\n# ' + inspect(opts002), function(t) { 38 | t.assertSurrounds( 39 | [ 'function foo (bar) {' 40 | , ' var a = 3;' 41 | , ' return bar + a;' 42 | , '}' 43 | ].join('\n') 44 | , opts002 45 | , [ '^function& foo (bar) {' 46 | , ' *var& a = 3;' 47 | , ' (return) bar + a;' 48 | , '}' 49 | ].join('\n')) 50 | t.end() 51 | }) 52 | 53 | t.end() 54 | }) 55 | -------------------------------------------------------------------------------- /test/redeyed-comments.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var result = redeyed(code, opts) 14 | this.equals(result.code, expected, inspect(code) + ' => ' + inspect(expected)) 15 | return this 16 | } 17 | 18 | t.end() 19 | }) 20 | 21 | test('\nstring config, Line comments', function(t) { 22 | var opts = { Line: { _default: '*:&' } } 23 | t.test('\n# ' + inspect(opts), function(t) { 24 | t.assertSurrounds( 25 | '// a comment' 26 | , opts 27 | , '*// a comment&' 28 | ) 29 | t.assertSurrounds( 30 | '// comment then new line\nif (a == 1) return' 31 | , opts 32 | , '*// comment then new line&\nif (a == 1) return' 33 | ) 34 | t.assertSurrounds( 35 | 'var n = new Test();// some comment after\n//more comment\nvar s = 3;' 36 | , opts 37 | , 'var n = new Test();*// some comment after&\n*//more comment&\nvar s = 3;' 38 | ) 39 | t.end() 40 | }) 41 | 42 | t.end() 43 | }) 44 | 45 | test('\nstring config, Block comments', function(t) { 46 | var opts = { Block: { _default: '_:-' } } 47 | t.test('\n# ' + inspect(opts), function(t) { 48 | t.assertSurrounds( 49 | '/* a comment */' 50 | , opts 51 | , '_/* a comment */-' 52 | ) 53 | t.assertSurrounds( 54 | '/* comment then new line*/\nif (a == 1) /* inline */ return' 55 | , opts 56 | , '_/* comment then new line*/-\nif (a == 1) _/* inline */- return' 57 | ) 58 | t.assertSurrounds( 59 | 'var n = new Test();/* some comment after*/\n/*more comment*/\nvar s = 3;' 60 | , opts 61 | , 'var n = new Test();_/* some comment after*/-\n_/*more comment*/-\nvar s = 3;' 62 | ) 63 | t.assertSurrounds( 64 | 'var a = 4;\n/* Multi line comment\n * Next line\n * and another\n*/ var morecode = "here";' 65 | , opts 66 | , 'var a = 4;\n_/* Multi line comment\n * Next line\n * and another\n*/- var morecode = "here";' 67 | ) 68 | t.end() 69 | }) 70 | 71 | t.end() 72 | }) 73 | -------------------------------------------------------------------------------- /test/redeyed-config-with-undefineds.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var optsi = inspect(opts) 14 | var result = redeyed(code, opts).code 15 | 16 | this.equals(result 17 | , expected 18 | , util.format('%s: %s => %s', optsi, inspect(code), inspect(expected)) 19 | ) 20 | return this 21 | } 22 | 23 | t.end() 24 | }) 25 | 26 | test('\n undefineds only', function(t) { 27 | t.assertSurrounds('1 + 2', { Numeric: { _default: undefined } }, '1 + 2') 28 | t.assertSurrounds('1 + 2', { Numeric: { _default: undefined }, _default: undefined }, '1 + 2') 29 | 30 | t.assertSurrounds( 31 | 'return true' 32 | , { 'Boolean': { 'true': undefined, 'false': undefined, _default: undefined }, _default: undefined } 33 | , 'return true' 34 | ) 35 | 36 | t.end() 37 | }) 38 | 39 | test('\n mixed', function(t) { 40 | t.assertSurrounds( 41 | 'return true || false' 42 | , { 'Boolean': { 'true': '&:', 'false': undefined, _default: undefined }, _default: undefined } 43 | , 'return &true || false' 44 | ) 45 | 46 | t.assertSurrounds( 47 | 'return true || false' 48 | , { 'Boolean': { 'true': '&:', 'false': undefined, _default: ':?' }, _default: undefined } 49 | , 'return &true? || false?' 50 | ) 51 | 52 | t.assertSurrounds( 53 | 'return true || false' 54 | , { 'Boolean': { 'true': '&:', 'false': undefined, _default: undefined }, _default: ':?' } 55 | , 'return &true? || false' 56 | ) 57 | 58 | t.end() 59 | }) 60 | -------------------------------------------------------------------------------- /test/redeyed-function-config-extra-params.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('function - config passing idx and tokens', function(t) { 12 | var args = [] 13 | var opts001 = { 14 | Boolean: { 15 | _default: identity 16 | } 17 | , Keyword: { 18 | _default: identity 19 | } 20 | , Identifier: { 21 | _default: identity 22 | } 23 | , Punctuator: { 24 | _default: identity 25 | } 26 | } 27 | var code = 'var fn = function () { return true; }' 28 | 29 | function identity(s, info) { 30 | args.push({ value: s, idx: info.tokenIndex, tokens: info.tokens, code: info.code }) 31 | // returning unchanged string will keep the splits be equal to the original tokens 32 | return s 33 | } 34 | 35 | t.test(inspect(opts001) + ' -- ' + code, function(t) { 36 | var result = redeyed(code, opts001, { splits: true }) 37 | var tokens = result.tokens 38 | 39 | t.equals(args.length, tokens.length, 'called with all tokens') 40 | 41 | for (var i = 0; i < tokens.length; i++) { 42 | var token = tokens[i] 43 | var arg = args[i] 44 | 45 | t.equals(arg.value, token.value, 'passes correct value: ' + inspect([ arg.value, token.value ])) 46 | t.equals(arg.idx, i, 'passes correct index') 47 | t.equals(arg.code, code, 'passes code') 48 | t.deepEquals(arg.tokens, tokens, 'passes all tokens') 49 | } 50 | t.end() 51 | }) 52 | t.end() 53 | }) 54 | -------------------------------------------------------------------------------- /test/redeyed-function-config-skipping-tokens.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var redeyed = require('..') 5 | 6 | test('given i skip 2 more tokens after each semicolon', function(t) { 7 | var calls = 0 8 | var opts = { 9 | Punctuator: { 10 | ';': function identity(s, info) { 11 | // tell it to skip past second to last token that is 2 ahead of the current one 12 | calls++ 13 | var skipToken = info.tokens[info.tokenIndex + 2] 14 | return skipToken ? { replacement: s, skipPastToken: skipToken } : s 15 | } 16 | } 17 | } 18 | 19 | ;[ { code: ';;;', expectedCalls: 1 } 20 | , { code: ';;;;', expectedCalls: 2 } 21 | , { code: '; ; ; ;', expectedCalls: 2 } 22 | , { code: ';;; ;;; ;;; ;', expectedCalls: 4 } 23 | , { code: ';;; ;;; ;;; ;;; ;', expectedCalls: 5 } 24 | , { code: ';;; ;;; ;;; ;;; ;;;', expectedCalls: 5 } 25 | , { code: ';;; ;;; ;;; ;;; ;;; ;', expectedCalls: 6 } 26 | ].forEach(function(x) { 27 | calls = 0 28 | redeyed(x.code, opts) 29 | t.equals(calls, x.expectedCalls, 'calls ' + x.expectedCalls + ' times for ' + x.code) 30 | }) 31 | t.end() 32 | }) 33 | 34 | test('replace log', function(t) { 35 | var opts = { 36 | Identifier: { 37 | console: function replaceLog(s, info) { 38 | var code = info.code 39 | var idx = info.tokenIndex 40 | var tokens = info.tokens 41 | var kind = tokens[idx + 2].value 42 | var firstArgTkn = tokens[idx + 4] 43 | var argIdx = idx + 3 44 | var open 45 | var tkn 46 | 47 | open = 1 48 | while (open) { 49 | tkn = tokens[++argIdx] 50 | 51 | if (tkn.value === '(') open++ 52 | if (tkn.value === ')') open-- 53 | } 54 | 55 | var argsIncludingClosingParen = code.slice(firstArgTkn.range[0], tkn.range[1]) 56 | var result = 'log.' + kind + '("main-logger", ' + argsIncludingClosingParen 57 | 58 | return { replacement: result, skipPastToken: tkn } 59 | } 60 | } 61 | } 62 | 63 | var origCode = [ 64 | 'console.info("info ", 1);' 65 | , 'console.warn("warn ", 3);' 66 | , 'console.error("error ", new Error("oh my!"));' 67 | ].join('\n') 68 | 69 | var expectedCode = [ 70 | 'log.info("main-logger", "info ", 1));' 71 | , 'log.warn("main-logger", "warn ", 3));' 72 | , 'log.error("main-logger", "error ", new Error("oh my!")));' 73 | ].join('\n') 74 | var code = redeyed(origCode, opts).code 75 | 76 | t.equals(code, expectedCode, 'transforms all log statements') 77 | t.end() 78 | }) 79 | -------------------------------------------------------------------------------- /test/redeyed-function-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var result = redeyed(code, opts).code 14 | this.equals(result, expected, inspect(code) + ' => ' + inspect(expected)) 15 | return this 16 | } 17 | 18 | t.end() 19 | }) 20 | 21 | test('\nfunction config, keywords', function(t) { 22 | var opts001 = { Keyword: { _default: function(s) { return '*' + s + '&' } } } 23 | t.test('\n# ' + inspect(opts001), function(t) { 24 | t.assertSurrounds('this', opts001, '*this&') 25 | 26 | t.assertSurrounds('this ', opts001, '*this& ') 27 | t.assertSurrounds(' this', opts001, ' *this&') 28 | t.assertSurrounds(' this ', opts001, ' *this& ') 29 | t.assertSurrounds('if (a == 1) return', opts001, '*if& (a == 1) *return&') 30 | t.assertSurrounds('var n = new Test();', opts001, '*var& n = *new& Test();') 31 | t.assertSurrounds( 32 | [ 'function foo (bar) {' 33 | , ' var a = 3;' 34 | , ' return bar + a;' 35 | , '}' 36 | ].join('\n') 37 | , opts001 38 | , [ '*function& foo (bar) {' 39 | , ' *var& a = 3;' 40 | , ' *return& bar + a;' 41 | , '}' 42 | ].join('\n')) 43 | t.end() 44 | }) 45 | 46 | var opts002 = { 47 | Keyword: { 48 | 'function': function(s) { return '^' + s + '&' } 49 | , 'return': function(s) { return '(' + s + ')' } 50 | , _default: function(s) { return '*' + s + '&' } 51 | } 52 | } 53 | 54 | t.test('\n# ' + inspect(opts002), function(t) { 55 | t.assertSurrounds( 56 | [ 'function foo (bar) {' 57 | , ' var a = 3;' 58 | , ' return bar + a;' 59 | , '}' 60 | ].join('\n') 61 | , opts002 62 | , [ '^function& foo (bar) {' 63 | , ' *var& a = 3;' 64 | , ' (return) bar + a;' 65 | , '}' 66 | ].join('\n')) 67 | t.end() 68 | }) 69 | 70 | t.end() 71 | }) 72 | 73 | test('#\n functin config - resolving', function(t) { 74 | var opts001 = { 75 | Keyword: { 76 | 'var': function(s) { return '^' + s + '&' } 77 | } 78 | , _default: function(s) { return '*' + s + '&' } 79 | } 80 | t.test('\n# specific but no type default and root default - root default not applied' + inspect(opts001), function(t) { 81 | t.assertSurrounds('var n = new Test();', opts001, '^var& n = new Test();').end() 82 | }) 83 | 84 | var opts002 = { 85 | Keyword: { 86 | 'var': function(s) { return '^' + s + '&' } 87 | , _default: function(s) { return '*' + s + '&' } 88 | } 89 | , _default: function(s) { return '(' + s + ')' } 90 | } 91 | t.test('\n# no type default but root default' + inspect(opts002), function(t) { 92 | t.assertSurrounds('var n = new Test();', opts002, '^var& n = *new& Test();').end() 93 | }) 94 | 95 | t.end() 96 | }) 97 | 98 | test('#\n function config - replacing', function(t) { 99 | var opts001 = { 100 | Keyword: { 101 | 'var': function() { return 'const' } 102 | } 103 | } 104 | t.test('\n# type default and root default (type wins)' + inspect(opts001), function(t) { 105 | t.assertSurrounds('var n = new Test();', opts001, 'const n = new Test();').end() 106 | }) 107 | 108 | var opts002 = { 109 | Keyword: { 110 | _default: function() { return 'const' } 111 | } 112 | } 113 | t.test('\n# type default' + inspect(opts002), function(t) { 114 | t.assertSurrounds('var n = new Test();', opts002, 'const n = const Test();').end() 115 | }) 116 | 117 | var opts003 = { 118 | Keyword: { 119 | 'new': function() { return 'NEW' } 120 | , _default: function() { return 'const' } 121 | } 122 | } 123 | t.test('\n# specific and type default' + inspect(opts003), function(t) { 124 | t.assertSurrounds('var n = new Test();', opts003, 'const n = NEW Test();').end() 125 | }) 126 | 127 | var opts004 = { 128 | Keyword: { 129 | _default: function(s) { return s.toUpperCase() } 130 | } 131 | , _default: function(s) { return 'not applied' } 132 | } 133 | t.test('\n# type default and root default (type wins)' + inspect(opts004), function(t) { 134 | t.assertSurrounds('var n = new Test();', opts004, 'VAR n = NEW Test();').end() 135 | }) 136 | 137 | var opts005 = { 138 | Keyword: { } 139 | , _default: function(s) { return s.toUpperCase() } 140 | } 141 | t.test('\n# no type default only root default - not applied' + inspect(opts005), function(t) { 142 | t.assertSurrounds('var n = new Test();', opts005, 'var n = new Test();').end() 143 | }) 144 | 145 | t.end() 146 | }) 147 | -------------------------------------------------------------------------------- /test/redeyed-incomplete.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var optsi = inspect(opts) 14 | var result = redeyed(code, opts).code 15 | 16 | this.equals(result 17 | , expected 18 | , util.format('%s: %s => %s', optsi, inspect(code), inspect(expected)) 19 | ) 20 | return this 21 | } 22 | 23 | t.end() 24 | }) 25 | 26 | test('incomplete statement', function(t) { 27 | t.test('\n# Keyword', function(t) { 28 | var keyconfig = { 'Keyword': { _default: '$:%' } } 29 | t.assertSurrounds('if(foo) { x', keyconfig, '$if%(foo) { x') 30 | t.assertSurrounds('class Foo { constructor(name)', keyconfig, '$class% Foo { constructor(name)') 31 | t.assertSurrounds('const x = ', keyconfig, '$const% x = ') 32 | t.assertSurrounds('function g() { yield', keyconfig, '$function% g() { $yield%') 33 | t.end() 34 | }) 35 | 36 | t.end() 37 | }) 38 | -------------------------------------------------------------------------------- /test/redeyed-jsx.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* eslint-disable no-template-curly-in-string */ 4 | 5 | var test = require('tape') 6 | var util = require('util') 7 | var redeyed = require('..') 8 | 9 | function inspect(obj) { 10 | return util.inspect(obj, false, 5, true) 11 | } 12 | 13 | test('adding custom asserts ... ', function(t) { 14 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 15 | var result = redeyed(code, opts, { jsx: true }).code 16 | if (expected == null) console.log(result) 17 | else this.equals(result, expected, inspect(code) + ' => ' + inspect(expected)) 18 | return this 19 | } 20 | 21 | t.end() 22 | }) 23 | 24 | test('\njsx support', function(t) { 25 | var config = { 26 | 'JSXIdentifier': { 27 | className: '$xc:cx%' 28 | , _default: '$x:x%' 29 | } 30 | , 'Punctuator': { _default: '$:%' 31 | } 32 | } 33 | t.assertSurrounds( 34 | '' 35 | , config 36 | , '$<%$xComponentx% $xstartx%$=%${%1$}% $xcclassNamecx%$=%"hello" $/%$>%' 37 | ) 38 | t.end() 39 | }) 40 | -------------------------------------------------------------------------------- /test/redeyed-keywords.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var optsi = inspect(opts) 14 | var result = redeyed(code, opts).code 15 | 16 | this.equals(result 17 | , expected 18 | , util.format('%s: %s => %s', optsi, inspect(code), inspect(expected)) 19 | ) 20 | return this 21 | } 22 | 23 | t.end() 24 | }) 25 | 26 | test('types', function(t) { 27 | t.test('\n# Keyword', function(t) { 28 | var keyconfig = { 'Keyword': { _default: '$:%' } } 29 | t.assertSurrounds('import foo from \'foo\';', keyconfig, '$import% foo from \'foo\';') 30 | t.assertSurrounds('export default foo;', keyconfig, '$export% $default% foo;') 31 | t.assertSurrounds('if(foo) { let bar = 1;}', keyconfig, '$if%(foo) { $let% bar = 1;}') 32 | t.assertSurrounds('const x = "foo";', keyconfig, '$const% x = "foo";') 33 | t.assertSurrounds('"use strict";(function* () { yield *v })', keyconfig, '"use strict";($function%* () { $yield% *v })') 34 | t.assertSurrounds('"use strict"; (class A { static constructor() { super() }})', keyconfig 35 | , '"use strict"; ($class% A { $static% constructor() { $super%() }})') 36 | t.assertSurrounds('class Foo { constructor(name){this.name = name;}}', keyconfig 37 | , '$class% Foo { constructor(name){$this%.name = name;}}') 38 | t.assertSurrounds('class Foo extends Bar { constructor(name,value){super(value);this.name = name;}}', keyconfig 39 | , '$class% Foo $extends% Bar { constructor(name,value){$super%(value);$this%.name = name;}}') 40 | t.end() 41 | }) 42 | 43 | t.end() 44 | }) 45 | -------------------------------------------------------------------------------- /test/redeyed-mixed.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var result = redeyed(code, opts).code 14 | this.equals(result, expected, inspect(code) + ' => ' + inspect(expected)) 15 | return this 16 | } 17 | 18 | t.end() 19 | }) 20 | 21 | test('\nmixed config, keywords', function(t) { 22 | var opts001 = { 23 | Keyword: { 24 | 'this': function(s) { return '_' + s } 25 | , 'if': { _before: '^' } 26 | , _default: '*:&' 27 | } 28 | } 29 | t.test('\n# ' + inspect(opts001), function(t) { 30 | t.assertSurrounds('if (this.hello) return "world";', opts001, '^if& (_this.hello) *return& "world";').end() 31 | }) 32 | 33 | var opts002 = { 34 | Keyword: { 35 | 'this': function(s) { return '_' + s } 36 | , 'if': { _before: '^' } 37 | , 'return': ':)' 38 | , _default: ':&' 39 | } 40 | , _default: '*:&' 41 | } 42 | t.test('\n# ' + inspect(opts002), function(t) { 43 | t.assertSurrounds('if (this.hello) return "world";', opts002, '^if& (_this.hello) *return) "world";').end() 44 | }) 45 | 46 | t.end() 47 | }) 48 | -------------------------------------------------------------------------------- /test/redeyed-result.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var redeyed = require('..') 5 | var esprima = require('esprima') 6 | 7 | test('redeyed result does not have esprima ast by default', function(t) { 8 | var code = '// a comment\nvar a = 3;' 9 | var conf = { Keyword: { _default: '_:-' } } 10 | 11 | var ast = esprima.parse(code, { tokens: true, comment: true, range: true, loc: true, tolerant: true }) 12 | var tokens = ast.tokens 13 | var comments = ast.comments 14 | 15 | var result = redeyed(code, conf) 16 | 17 | t.equal(typeof result.ast, 'undefined', 'ast') 18 | t.deepEquals(result.tokens, tokens, 'tokens') 19 | t.deepEquals(result.comments, comments, 'comments') 20 | t.notEquals(result.code, undefined, 'code') 21 | t.end() 22 | }) 23 | 24 | test('redeyed result has esprima ast, tokens, comments and splits and transformed code', function(t) { 25 | var code = '// a comment\nvar a = 3;' 26 | var conf = { Keyword: { _default: '_:-' } } 27 | var ast = esprima.parse(code, { tokens: true, comment: true, range: true, loc: true, tolerant: true }) 28 | var tokens = ast.tokens 29 | var comments = ast.comments 30 | 31 | var result = redeyed(code, conf, { buildAst: true }) 32 | 33 | t.deepEquals(result.ast, ast, 'ast') 34 | t.deepEquals(result.tokens, tokens, 'tokens') 35 | t.deepEquals(result.comments, comments, 'comments') 36 | t.notEquals(result.code, undefined, 'code') 37 | 38 | t.end() 39 | }) 40 | 41 | test('redeyed result - { nojoin } has esprima ast, tokens, comments and splits but no transformed code', function(t) { 42 | var code = '// a comment\nvar a = 3;' 43 | var conf = { Keyword: { _default: '_:-' } } 44 | var ast = esprima.parse(code, { tokens: true, comment: true, range: true, loc: true, tolerant: true }) 45 | var tokens = ast.tokens 46 | var comments = ast.comments 47 | 48 | var result = redeyed(code, conf, { nojoin: true, buildAst: true }) 49 | 50 | t.deepEquals(result.ast, ast, 'ast') 51 | t.deepEquals(result.tokens, tokens, 'tokens') 52 | t.deepEquals(result.comments, comments, 'comments') 53 | t.equals(result.code, undefined, 'code') 54 | 55 | t.end() 56 | }) 57 | -------------------------------------------------------------------------------- /test/redeyed-script-level-return.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('properly handles script level return -- no blow up', function(t) { 12 | var code = [ 13 | '' 14 | , 'return 1;' 15 | ].join('\n') 16 | var opts = { Keyword: { 'return': '%:^' } } 17 | var expected = '\n%return^ 1;' 18 | var res = redeyed(code, opts).code 19 | 20 | t.equals(res, expected, inspect(code) + ' opts: ' + inspect(opts) + ' => ' + inspect(expected)) 21 | t.end() 22 | }) 23 | -------------------------------------------------------------------------------- /test/redeyed-shebang.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('preserves shebang', function(t) { 12 | var code = [ 13 | '#!/usr/bin/env node' 14 | , 'var util = require("util");' 15 | ].join('\n') 16 | var opts = { Keyword: { 'var': '%:^' } } 17 | var expected = [ 18 | '#!/usr/bin/env node' 19 | , '%var^ util = require("util");' 20 | ].join('\n') 21 | var res = redeyed(code, opts).code 22 | 23 | t.equals(res, expected, inspect(code) + ' opts: ' + inspect(opts) + ' => ' + inspect(expected)) 24 | t.end() 25 | }) 26 | -------------------------------------------------------------------------------- /test/redeyed-smoke.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // applying redeyed to a bunch of files of contained libraries as a smoke test 4 | var test = require('tape') 5 | var path = require('path') 6 | var fs = require('fs') 7 | var readdirp = require('readdirp') 8 | var redeyed = require('..') 9 | var nodeModules = path.join(__dirname, '..', 'node_modules') 10 | var esprimadir = path.join(nodeModules, 'esprima') 11 | 12 | test('esprima', function(t) { 13 | readdirp({ root: esprimadir, fileFilter: '*.js' }) 14 | .on('data', function(entry) { 15 | var code = fs.readFileSync(entry.fullPath, 'utf-8') 16 | var resultAst = redeyed(code, { Keyword: { 'var': '+:-' } }, { buildAst: true }).code 17 | var resultTokenize = redeyed(code, { Keyword: { 'var': '+:-' } }, { buildAst: false }).code 18 | 19 | t.assert(~resultAst.indexOf('+var-') || !(~resultAst.indexOf('var ')), 'redeyed ' + entry.path) 20 | t.assert(~resultTokenize.indexOf('+var-') || !(~resultTokenize.indexOf('var ')), 'redeyed ' + entry.path) 21 | }) 22 | .on('end', t.end.bind(t)) 23 | }) 24 | 25 | test('redeyed', function(t) { 26 | readdirp({ 27 | root: path.join(__dirname, '..') 28 | , fileFilter: '*.js' 29 | , directoryFilter: [ '!.git', '!node_modules' ] 30 | }) 31 | .on('data', function(entry) { 32 | var code = fs.readFileSync(entry.fullPath, 'utf-8') 33 | var result = redeyed(code, { Keyword: { 'var': '+:-' } }).code 34 | 35 | t.assert(~result.indexOf('+var-') || !(~result.indexOf('var ')), 'redeyed ' + entry.path) 36 | }) 37 | .on('end', t.end.bind(t)) 38 | }) 39 | -------------------------------------------------------------------------------- /test/redeyed-string-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var util = require('util') 5 | var redeyed = require('..') 6 | 7 | function inspect(obj) { 8 | return util.inspect(obj, false, 5, true) 9 | } 10 | 11 | test('adding custom asserts ... ', function(t) { 12 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 13 | var result = redeyed(code, opts).code 14 | this.equals(result, expected, inspect(code) + ' => ' + inspect(expected)) 15 | return this 16 | } 17 | 18 | t.end() 19 | }) 20 | 21 | test('\nstring config, keywords', function(t) { 22 | var opts001 = { Keyword: { _default: '*:&' } } 23 | t.test('\n# ' + inspect(opts001), function(t) { 24 | t.assertSurrounds('this', opts001, '*this&') 25 | t.assertSurrounds('if (a == 1) return', opts001, '*if& (a == 1) *return&') 26 | t.assertSurrounds('var n = new Test();', opts001, '*var& n = *new& Test();') 27 | t.end() 28 | }) 29 | 30 | var opts002 = { 31 | Keyword: { 32 | 'function': '^:' 33 | , 'return': '(:)' 34 | , _default: '*:&' 35 | } 36 | } 37 | 38 | t.test('\n# ' + inspect(opts002), function(t) { 39 | t.assertSurrounds( 40 | [ 'function foo (bar) {' 41 | , ' var a = 3;' 42 | , ' return bar + a;' 43 | , '}' 44 | ].join('\n') 45 | , opts002 46 | , [ '^function& foo (bar) {' 47 | , ' *var& a = 3;' 48 | , ' (return) bar + a;' 49 | , '}' 50 | ].join('\n')) 51 | t.end() 52 | }) 53 | 54 | t.end() 55 | }) 56 | 57 | test('\nstring configs resolve from type and root', function(t) { 58 | var code = 'var a = new Test();' 59 | 60 | function run(t, conf, expected, code_) { 61 | t.test('\n# ' + inspect(conf), function(t) { 62 | t.assertSurrounds(code_ || code, conf, expected) 63 | t.end() 64 | }) 65 | } 66 | 67 | // at least the token kind has to be configured in order for the root_default to be applied 68 | // otherwise a root._default would affect all tokens, even the ones we want to leave unchanged 69 | run(t, { _default: '*:' }, 'var a = new Test();') 70 | 71 | t.test('\n\n# only before or after specified, but no root._default', function(t) { 72 | run(t, { Keyword: { _default: '*:' } }, '*var a = *new Test();') 73 | run(t, { Keyword: { _default: ':-' } }, 'var- a = new- Test();') 74 | t.end() 75 | }) 76 | 77 | t.test('\n\n# resolve missing from root._default', function(t) { 78 | run(t, { Keyword: { _default: '*:' }, _default: '(:-' }, '*var- a = *new- Test();') 79 | run(t, { Keyword: { _default: ':-' }, _default: '*:)' }, '*var- a = *new- Test();') 80 | t.end() 81 | }) 82 | 83 | t.test('\n\n# no resolve if all specified', function(t) { 84 | run(t, { Keyword: { _default: '+:-' }, _default: '*:)' }, '+var- a = +new- Test();') 85 | run(t, { Keyword: { _default: ':-' }, _default: ':)' }, 'var- a = new- Test();') 86 | t.end() 87 | }) 88 | 89 | t.test('\n\n# resolve specific token no defaults', function(t) { 90 | run(t, { Keyword: { 'var': '*:' } }, '*var a = new Test();') 91 | run(t, { Keyword: { 'var': ':-' } }, 'var- a = new Test();') 92 | t.end() 93 | }) 94 | 95 | t.test('\n\n# resolve specific token with type defaults', function(t) { 96 | run(t, { Keyword: { 'var': '*:', _default: ':-' } }, '*var- a = new- Test();') 97 | run(t, { Keyword: { 'var': '*:', _default: '(:-' } }, '*var- a = (new- Test();') 98 | run(t, { Keyword: { 'var': ':-', _default: '*:' } }, '*var- a = *new Test();') 99 | run(t, { Keyword: { 'var': ':-', _default: '*:)' } }, '*var- a = *new) Test();') 100 | run(t, { Keyword: { 'var': ':-', 'new': ':&', _default: '*:' } }, '*var- a = *new& Test();') 101 | t.end() 102 | }) 103 | 104 | t.test( 105 | '\n\n# resolve specific token with root defaults, but no type default - root default not applied to unspecified tokens' 106 | , function(t) { 107 | run(t, { Keyword: { 'var': '*:' }, _default: ':-' }, '*var- a = new Test();') 108 | run(t, { Keyword: { 'var': ':-' }, _default: '*:' }, '*var- a = new Test();') 109 | t.end() 110 | } 111 | ) 112 | 113 | t.test('\n\n# resolve specific token with type and root defaults', function(t) { 114 | run(t, { Keyword: { 'var': '*:', _default: '+:-' }, _default: ':)' }, '*var- a = +new- Test();') 115 | run(t, { Keyword: { 'var': ':-', _default: '*:+' }, _default: '(:' }, '*var- a = *new+ Test();') 116 | t.end() 117 | }) 118 | 119 | t.test('all exact tokens undefined, but type default', function(t) { 120 | run(t, { 'Boolean': { 'true': undefined, 'false': undefined, _default: '+:-' } }, 'return +true- || +false-;', 'return true || false;') 121 | t.end() 122 | }) 123 | 124 | t.end() 125 | }) 126 | -------------------------------------------------------------------------------- /test/redeyed-types.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* eslint-disable no-template-curly-in-string */ 4 | 5 | var test = require('tape') 6 | var util = require('util') 7 | var redeyed = require('..') 8 | 9 | function inspect(obj) { 10 | return util.inspect(obj, false, 5, true) 11 | } 12 | 13 | test('adding custom asserts ... ', function(t) { 14 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 15 | var optsi = inspect(opts) 16 | var result = redeyed(code, opts).code 17 | 18 | this.equals(result 19 | , expected 20 | , util.format('%s: %s => %s', optsi, inspect(code), inspect(expected)) 21 | ) 22 | return this 23 | } 24 | 25 | t.end() 26 | }) 27 | 28 | test('types', function(t) { 29 | t.test('\n# Boolean', function(t) { 30 | t.assertSurrounds('return true;', { 'Boolean': { _default: '$:%' } }, 'return $true%;') 31 | t.assertSurrounds('return true; return false;' 32 | , { 'Boolean': { 'false': '#:', _default: '$:%' } } 33 | , 'return $true%; return #false%;') 34 | t.end() 35 | }) 36 | 37 | t.test('\n# Identifier', function(t) { 38 | t.assertSurrounds('var a = 1;', { 'Identifier': { _default: '$:%' } }, 'var $a% = 1;') 39 | t.assertSurrounds('var a = 1; const b = 2;' 40 | , { 'Identifier': { 'b': '#:', _default: '$:%' } } 41 | , 'var $a% = 1; const #b% = 2;') 42 | t.end() 43 | }) 44 | 45 | t.test('\n# Null', function(t) { 46 | t.assertSurrounds('return null;', { 'Null': { _default: '$:%' } }, 'return $null%;').end() 47 | }) 48 | 49 | t.test('\n# Numeric', function(t) { 50 | t.assertSurrounds('return 1;', { 'Numeric': { _default: '$:%' } }, 'return $1%;') 51 | t.assertSurrounds('return 1; return 2;' 52 | , { 'Numeric': { '2': '#:', _default: '$:%' } } 53 | , 'return $1%; return #2%;') 54 | t.end() 55 | }) 56 | 57 | t.test('\n# Punctuator', function(t) { 58 | var punctuator = { 'Punctuator': { _default: '$:%' } } 59 | t.assertSurrounds('return 2 * 2;', punctuator, 'return 2 $*% 2$;%') 60 | t.assertSurrounds('return 2 * 2;' 61 | , { 'Punctuator': { '*': '#:', _default: '$:%' } } 62 | , 'return 2 #*% 2$;%') 63 | t.assertSurrounds('var {op, lhs, rhs} = getASTNode()', punctuator, 'var ${%op$,% lhs$,% rhs$}% $=% getASTNode$(%$)%') 64 | t.assertSurrounds('function f(x, y=12) { return x + y;}', punctuator, 'function f$(%x$,% y$=%12$)% ${% return x $+% y$;%$}%') 65 | t.assertSurrounds('function f(x, ...y) { return x * y.length;}', punctuator, 'function f$(%x$,% $...%y$)% ${% return x $*% y$.%length$;%$}%') 66 | t.end() 67 | }) 68 | 69 | t.test('\n# String', function(t) { 70 | t.assertSurrounds('return "hello";', { 'String': { _default: '$:%' } }, 'return $"hello"%;') 71 | t.assertSurrounds('return "hello"; return "world";' 72 | , { 'String': { '"world"': '#:', _default: '$:%' } } 73 | , 'return $"hello"%; return #"world"%;') 74 | t.end() 75 | }) 76 | 77 | t.end() 78 | }) 79 | -------------------------------------------------------------------------------- /test/redeyed-upcoming.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* eslint-disable no-template-curly-in-string */ 3 | 4 | var test = require('tape') 5 | var util = require('util') 6 | var redeyed = require('..') 7 | 8 | function inspect(obj) { 9 | return util.inspect(obj, false, 5, true) 10 | } 11 | 12 | test('adding custom asserts ... ', function(t) { 13 | t.constructor.prototype.assertSurrounds = function(code, opts, expected) { 14 | var optsi = inspect(opts) 15 | var result = redeyed(code, opts).code 16 | 17 | this.equals(result 18 | , expected 19 | , util.format('%s: %s => %s', optsi, inspect(code), inspect(expected)) 20 | ) 21 | return this 22 | } 23 | 24 | t.end() 25 | }) 26 | 27 | test('upcoming syntax: rest and spread properties', function(t) { 28 | t.test('\n# Punctuator', function(t) { 29 | var punctuator = { 'Punctuator': { _default: '$:%' } } 30 | t.assertSurrounds('{a,...b} = c', punctuator, '${%a$,%$...%b$}% $=% c') 31 | t.assertSurrounds('x={y,...z}', punctuator, 'x$=%${%y$,%$...%z$}%') 32 | t.assertSurrounds('x ** y', punctuator, 'x $**% y') 33 | t.assertSurrounds('x **= y', punctuator, 'x $**=% y') 34 | t.end() 35 | }) 36 | 37 | t.test('\n# Identifier', function(t) { 38 | var identifier = { 'Identifier': { _default: '$:%' } } 39 | t.assertSurrounds('{a,...b} = c', identifier, '{$a%,...$b%} = $c%') 40 | t.assertSurrounds('x={y,...z}', identifier, '$x%={$y%,...$z%}') 41 | t.end() 42 | }) 43 | 44 | t.end() 45 | }) 46 | --------------------------------------------------------------------------------