├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.MD ├── README.md ├── bin └── jsfmt ├── examples └── styleGuide.js ├── lib ├── ast.js ├── config.js ├── defaultStyle.json ├── format.js ├── helpers.js ├── index.js ├── parser.js ├── rewrite.js ├── run.js └── validate.js ├── package.json └── tests ├── ast.js ├── format.js ├── rewrite.js ├── search.js └── validate.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | lib-cov 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use new container based infrastructure 2 | # http://docs.travis-ci.com/user/migrating-from-legacy/ 3 | sudo: false 4 | language: node_js 5 | node_js: 6 | - 'stable' 7 | - '4.1' 8 | - '4.0' 9 | - '0.12' 10 | - '0.11' 11 | - '0.10' 12 | - 'iojs' 13 | after_script: 14 | - jscoverage --no-highlight lib lib-cov 15 | - JSFMT_COV=1 mocha -R mocha-lcov-reporter -r jscoverage --covinject=true ./tests | coveralls 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | === 3 | 4 | This document outlines how to contribute to jsfmt. New contributors are always welcome. 5 | 6 | 7 | SOURCES 8 | === 9 | 10 | Based on [http://www.contribution-guide.org/]() and the [node.js CONTRIBUTING.md](https://github.com/joyent/node/blob/master/CONTRIBUTING.md). 11 | 12 | These are great documents to get more details about how to contribute. 13 | 14 | 15 | BUGS 16 | === 17 | 18 | - Check if the issue has already been filed here: [jsfmt issues](https://github.com/rdio/jsfmt/issues/) 19 | - If an issue already exists let us know if you're also experiencing the same issue and any additional details you can provide. 20 | - If its a new issue, please file a new issue with as much detail as possible including steps to reproduce the problem. 21 | 22 | 23 | CONTRIBUTING CHANGES 24 | === 25 | 26 | 1. Hit 'fork' on Github, creating e.g. /jsfmt. 27 | 2. `git clone git@github.com:/jsfmt` 28 | 3. `cd jsfmt` 29 | 4. `git checkout -b ` 30 | 5. Write new tests expecting the correct functionality and make sure they fail. 31 | 6. Fix the issue until the tests pass. 32 | 7. `git commit -m "" # where is a detailed description of the changes` 33 | 8. `git push origin ` 34 | 9. Visit your fork on Github and click "Pull request" including the issue number in the description field and hit "Submit" 35 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | jshint: { 4 | lib: { 5 | src: ['lib/**/*.js'], 6 | }, 7 | tests: { 8 | src: ['tests/**/*.js'], 9 | }, 10 | }, 11 | mochaTest: { 12 | tests: { 13 | options: { 14 | reporter: 'spec', 15 | }, 16 | src: ['tests/**/*.js'], 17 | }, 18 | }, 19 | exec: { 20 | // Tasks to run `jsfmt` 21 | jsfmtLib: './bin/jsfmt -w ./lib/**/*.js', 22 | jsfmtTests: './bin/jsfmt -w ./tests/**/*.js', 23 | jsfmtGrunt: './bin/jsfmt -w ./Gruntfile.js', 24 | jsfmtExamples: './bin/jsfmt -w ./examples/**/*.js', 25 | 26 | // Task to verify there is no git diff 27 | // DEV: This is best to run after `grunt fmt` to help ensure nothing changed 28 | // DEV: Use `bash -c ""` to force running in `bash` on travis 29 | verifyNoChanges: 'bash -c "git --no-pager diff && test \"\$(git diff)\" == \"\""', 30 | }, 31 | }); 32 | 33 | grunt.loadNpmTasks('grunt-contrib-jshint'); 34 | grunt.loadNpmTasks('grunt-exec'); 35 | grunt.loadNpmTasks('grunt-mocha-test'); 36 | grunt.registerTask('default', ['jshint', 'mochaTest']); 37 | grunt.registerTask('fmt', ['exec:jsfmtLib', 'exec:jsfmtTests', 'exec:jsfmtGrunt', 'exec:jsfmtExamples']); 38 | grunt.registerTask('verify', ['exec:verifyNoChanges']); 39 | }; 40 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | Copyright 2014 Rdio, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jsfmt 2 | === 3 | 4 | [![NPM version](https://badge.fury.io/js/jsfmt.svg)](http://badge.fury.io/js/jsfmt) 5 | [![Build Status](https://travis-ci.org/rdio/jsfmt.svg?branch=master)](https://travis-ci.org/rdio/jsfmt) 6 | [![Dependency Status](https://david-dm.org/rdio/jsfmt.svg)](https://david-dm.org/rdio/jsfmt) 7 | [![Coverage Status](https://coveralls.io/repos/rdio/jsfmt/badge.svg)](https://coveralls.io/r/rdio/jsfmt) 8 | 9 | For formatting, searching, and rewriting JavaScript. Analogous to [`gofmt`](http://golang.org/cmd/gofmt/). 10 | 11 | Installation 12 | --- 13 | 14 | `npm install -g jsfmt` 15 | 16 | Usage 17 | --- 18 | 19 | ``` 20 | $ jsfmt --help 21 | Usage: 22 | jsfmt [--no-format] [--save-ast] [--diff|--list|--write] [--validate] [--rewrite PATTERN|--search PATTERN] [--json|--ast] [...] 23 | jsfmt (--version | --help) 24 | 25 | Options: 26 | -h --help Show this help text 27 | --version Show jsfmt version 28 | -d --diff Show diff against original file 29 | -l --list List the files which differ from jsfmt output 30 | -v --validate Validate the input file(s) 31 | --no-format Do not format the input file(s) 32 | -w --write Overwrite the original file with jsfmt output 33 | -j --json Tell jsfmt that the file being parsed is json 34 | -a --ast Tell jsfmt that the file being parsed is in JSON AST 35 | --save-ast Output the resulting js in JSON AST format 36 | -r=PATTERN --rewrite PATTERN Rewrite rule (e.g., 'a.slice(b, len(a) -> a.slice(b)') 37 | -s=PATTERN --search PATTERN Search rule (e.g., 'a.slice') 38 | ``` 39 | 40 | If no path is given it will read from `stdin`. A directory path will recurse over all *.js files in the directory. 41 | 42 | Note that the AST options (`--ast` and `--save-ast`) are experimental and may be removed. 43 | 44 | Formatting 45 | --- 46 | 47 | For formatting `jsfmt` uses [esformatter](https://github.com/millermedeiros/esformatter). 48 | 49 | ### .jsfmtrc 50 | 51 | Any of the [esformatter](https://github.com/millermedeiros/esformatter) formatting 52 | options can be overwritten via a `.jsfmtrc` file. The file is parsed using 53 | [rc](https://github.com/dominictarr/rc), which accepts either a `json` or `ini` formatted file. 54 | 55 | A `.jsfmtrc` will be read if it exists in any of the following directories: 56 | * a local .jsfmtrc or the first found looking in ./ ../ ../../ ../../../ etc. 57 | * $HOME/.jsfmtrc 58 | * $HOME/.jsfmt/config 59 | * $HOME/.config/jsfmt 60 | * $HOME/.config/jsfmt/config 61 | * /etc/jsfmtrc 62 | * /etc/jsfmt/config 63 | 64 | `jsfmt` will also attempt to pickup and use the configured `indent` 65 | variable from your `.jshintrc` configuration file, if present. 66 | 67 | Rewriting 68 | --- 69 | 70 | The `--rewrite` flag allows rewriting portions of the JavaScript's AST before formatting. This is especially handy for intelligent renaming and handling API changes from a library. The rewrite rule must be a string of the form: 71 | 72 | pattern -> replacement 73 | 74 | Both `pattern` and `replacement` must be valid JavaScript. In `pattern`, single-character lowercase identifiers serve as wildcards matching arbitrary expressions; those expressions will be substituted for the same identifiers in the `replacement`. 75 | 76 | ### Example 77 | 78 | Rewrite occurences of `_.reduce` to use native reduce: 79 | 80 | jsfmt --rewrite "_.reduce(a, b, c) -> a.reduce(b, c)" reduce.js 81 | 82 | Searching 83 | --- 84 | 85 | The `--search` flag allows searching through a JavaScript's AST. The search rule is very similar to the rewrite rule but just outputs expressions that match the given search expression. The search expression must be valid JavaScript. 86 | 87 | ### Example 88 | 89 | Find occurences of `_.reduce`: 90 | 91 | jsfmt --search "_.reduce(a, b, c)" reduce.js 92 | 93 | Validating 94 | --- 95 | 96 | The `--validate` flag will print any errors found by esprima while parsing the JavaScript. 97 | 98 | ### Example 99 | 100 | jsfmt --validate bad.js 101 | 102 | API 103 | --- 104 | 105 | ### Formatting 106 | 107 | ```javascript 108 | jsfmt.format(, ) // Returns formatted JavaScript 109 | ``` 110 | 111 | ```javascript 112 | jsfmt.formatJSON(, ) // Returns formatted JSON 113 | ``` 114 | 115 | ```javascript 116 | var config = jsfmt.getConfig(); // Loads the jsfmt config from the appropriate rc file or default config object 117 | ``` 118 | 119 | #### Example 120 | 121 | ```javascript 122 | var jsfmt = require('jsfmt'); 123 | var fs = require('fs'); 124 | 125 | var js = fs.readFileSync('unformatted.js'); 126 | var config = jsfmt.getConfig(); 127 | 128 | js = jsfmt.format(js, config); 129 | ``` 130 | 131 | ### Rewriting 132 | 133 | ```javascript 134 | jsfmt.rewrite(, ) // Returns rewritten JavaScript 135 | ``` 136 | 137 | #### Example 138 | 139 | ```javascript 140 | var jsfmt = require('jsfmt'); 141 | var fs = require('fs'); 142 | 143 | var js = fs.readFileSync('each.js'); 144 | 145 | js = jsfmt.rewrite(js, "_.each(a, b) -> a.forEach(b)"); 146 | ``` 147 | 148 | ### Searching 149 | 150 | ```javascript 151 | jsfmt.search(, ) // Returns array of matches 152 | ``` 153 | 154 | #### Example 155 | 156 | ```javascript 157 | var jsfmt = require('jsfmt'); 158 | var fs = require('fs'); 159 | 160 | var js = fs.readFileSync('component.js'); 161 | 162 | jsfmt.search(js, "R.Component.create(a, { dependencies: z })").forEach(function(matches, wildcards) { 163 | console.log(wildcards.z); 164 | }); 165 | ``` 166 | 167 | ### Validating 168 | 169 | ```javascript 170 | jsfmt.validate() // Returns errors found while parsing JavaScript 171 | ``` 172 | 173 | ```javascript 174 | jsfmt.validateJSON() // Returns errors found while parsing JSON 175 | ``` 176 | 177 | #### Example 178 | 179 | ```javascript 180 | var jsfmt = require('jsfmt'); 181 | var fs = require('fs'); 182 | 183 | var js = fs.readFileSync('each.js'); 184 | var errors = jsfmt.validate(js); 185 | 186 | for (var i = 0; i < errors.length; i++) { 187 | console.error(errors[i]); 188 | } 189 | ``` 190 | 191 | Plugins 192 | ------- 193 | 194 | Since `jsfmt` uses `esformatter` under the covers for formatting your code you can utilize any `esformatter` plugins with `jsfmt`. Please see https://github.com/millermedeiros/esformatter/#plugins for more information. 195 | 196 | ### JSX 197 | 198 | There exists a plugin [esformatter-jsx](https://github.com/royriojas/esformatter-jsx) which provides support for formatting JSX with `esformatter`. Please see https://github.com/royriojas/esformatter-jsx/wiki/Usage-with-jsfmt for more information on setting up with `jsfmt`. 199 | 200 | Links 201 | --- 202 | 203 | - vim-jsfmt.vim - https://github.com/mephux/vim-jsfmt - "Format javascript source on save." 204 | - Atom Package - https://atom.io/packages/atom-jsfmt - "Automatically run jsfmt every time you save a JavaScript source file." 205 | - Grunt Task - https://github.com/james2doyle/grunt-jsfmt - "A task for the jsfmt library." 206 | - Emacs Plugin - https://github.com/brettlangdon/jsfmt.el - "Run jsfmt from within emacs" 207 | - Gulp Task - https://github.com/blai/gulp-jsfmt - "A gulp task for jsfmt." 208 | - Sublime Text plugin - https://github.com/ionutvmi/sublime-jsfmt - "On-demand and automatic jsfmt from Sublime Text 2 and 3" 209 | 210 | Changelog 211 | --- 212 | 213 | ### v0.4.0 214 | 215 | - Added two new command-line args for AST formatting. Note that these are experimental and may be removed. 216 | - Removed `--config` option in favor of .jsfmtrc and better docs around rc. 217 | - Updated esformatter and using new esformatter plugin for automatic brace insertion. 218 | - Updated style guide to include esformatter changes. 219 | - Fixes and cleanup for shebang. 220 | - Support for variable arguments using ES6 rest syntax. 221 | - General rewrite cleanup. 222 | - Changing exit code to `-1` on missing arg failure. 223 | - Updates to `rc` and other dependencies. 224 | 225 | ### v0.3.2 226 | 227 | - Adding support for `UnaryExpression` 228 | - Fixing bug where rewrite types were not being set properly 229 | 230 | ### v0.3.1 231 | 232 | - Fixed bug when searching for expressions within BlockStatement or Program body 233 | - Added JSON support 234 | 235 | ### v0.3.0 236 | 237 | - Added CONTRIBUTING 238 | - Added tests 239 | - Added Gruntfile for development 240 | - Added CI support 241 | - Added style guide 242 | - Added default formatting config 243 | - Exposed `jsfmt.getConfig` api method for loading jsfmt config 244 | - Exposed `jsfmt.format(js[, options])` api method for formatting 245 | - Added `--validate` option and exposed `jsfmt.validate` api method 246 | - Pinned dependencies 247 | 248 | ### v0.2.0 249 | 250 | - Add [rc](https://github.com/dominictarr/rc) and `--config config.json` support for formatting configuration 251 | - Making `--format` the default action 252 | - Fix support for shebang at the top of js files, e.g. `#!/usr/bin/env node` 253 | - Fix jsfmt diff mode where whitespace was unaccounted for due to `-b` git diff option 254 | 255 | ### v0.1.1 256 | 257 | - Initial release 258 | 259 | License 260 | --- 261 | Apache License, Version 2.0. Copyright 2014 Rdio, Inc. 262 | -------------------------------------------------------------------------------- /bin/jsfmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/run.js'); 3 | -------------------------------------------------------------------------------- /examples/styleGuide.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var test = "This is a sample declaration."; 4 | 5 | var arr = [ 6 | 0, 7 | 1, 8 | 2, 9 | 3 10 | ]; 11 | 12 | var a; 13 | var b; 14 | var c; 15 | var another = test; 16 | 17 | var sum = 0; 18 | for (var i = 0; i < test.length; i++) { 19 | sum += i; 20 | } 21 | 22 | function fun(num) { 23 | if (num > 100) { 24 | return num; 25 | } 26 | 27 | return num * fun(num); 28 | } 29 | 30 | var anotherFun = function(a, b, c) { 31 | return a == "a" || 32 | b == "b" || 33 | c == "c"; 34 | }; 35 | 36 | var myObj = { 37 | hello: "world" 38 | }; 39 | 40 | switch (myObj.hello) { 41 | case "world": 42 | alert(test); 43 | break; 44 | default: 45 | alert(myObj.hello); 46 | break; 47 | } 48 | 49 | var that = "Empty"; 50 | 51 | function then(that) { 52 | return that; 53 | } 54 | 55 | // This is a test of conditional wrapping 56 | if (this) { 57 | then(that); 58 | 59 | } else if (that) { 60 | then(this); 61 | 62 | } else { 63 | console.error("Wat?"); 64 | } 65 | 66 | console.log("After if/else if/else conditional"); 67 | 68 | if (this) { 69 | then(that); 70 | } else if (that) { 71 | then(this); 72 | } 73 | 74 | console.log("After if/else if conditional"); 75 | 76 | try { 77 | throw new Error("Whoa now"); 78 | } catch ( err ) { 79 | console.error(err); 80 | } 81 | 82 | var str = '' + fun([ 83 | 1, 2, 3 84 | ]) + ''; 85 | 86 | // TODO: Keep indentation of BinaryOperators inside function call args 87 | $(document.body).append('
  • ' + 88 | myVar.toString() + 89 | '
  • '); 90 | 91 | var myVar = new myVarTypes['A type'](); 92 | 93 | callFunc( 94 | 'An arg', 95 | 'Another arg', 96 | [ 97 | 'Some final args' 98 | ] 99 | ); 100 | 101 | // TODO: Indent in-line with `if` or don't wrap object expression inside conditional 102 | if (!this.model.set(values, { 103 | validate: true 104 | })) { 105 | return; 106 | } 107 | 108 | var arrayOfObjects = [{ 109 | a: true 110 | }, { 111 | a: false 112 | }]; 113 | 114 | var nestedObjects = { 115 | one: { 116 | fish: { 117 | two: 'fish' 118 | } 119 | }, 120 | red: { 121 | fish: { 122 | blue: 'fish' 123 | } 124 | } 125 | }; 126 | 127 | }).call(this); 128 | 129 | (function() { 130 | console.log('This is another block'); 131 | })(); 132 | -------------------------------------------------------------------------------- /lib/ast.js: -------------------------------------------------------------------------------- 1 | var escodegen = require('escodegen'); 2 | var esprima = require('esprima'); 3 | 4 | 5 | module.exports.parseAST = function(ast) { 6 | var js = escodegen.generate(ast, { 7 | comment: true, 8 | format: { 9 | quotes: 'double' 10 | } 11 | }); 12 | return js; 13 | }; 14 | 15 | module.exports.generateAST = function(js) { 16 | var ast = esprima.parse(js, { 17 | raw: true, 18 | tokens: true, 19 | range: true, 20 | comment: true, 21 | sourceType: 'module' 22 | }); 23 | ast = escodegen.attachComments(ast, ast.comments, ast.tokens); 24 | return ast; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var rc = require('rc'); 2 | var deepExtend = require('deep-extend'); 3 | 4 | var defaultStyle = require('./defaultStyle.json'); 5 | 6 | var config = null; 7 | 8 | var loadConfig = function() { 9 | // attempt to pickup on indent level from existing .jshintrc file 10 | defaultStyle.indent = defaultStyle.indent || {}; 11 | // rc(name, default, argv), use {} to stop argv from being loaded 12 | var jshintSettings = rc('jshint', {}, {}); 13 | if (jshintSettings.indent) { 14 | defaultStyle.indent.value = new Array(parseInt(jshintSettings.indent) + 1).join(' '); 15 | } 16 | 17 | // rc(name, default, argv), use {} to stop argv from being loaded 18 | var config = rc('jsfmt', {}, {}); 19 | 20 | //allow overriding the list of plugins via local config 21 | if (config.plugins) { 22 | defaultStyle.plugins = config.plugins; 23 | } 24 | 25 | return deepExtend(defaultStyle, config); 26 | }; 27 | 28 | exports.getConfig = function() { 29 | return config || (config = loadConfig()); 30 | }; 31 | -------------------------------------------------------------------------------- /lib/defaultStyle.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "default", 3 | "plugins": ["esformatter-braces", "esformatter-var-each"], 4 | "indent": { 5 | "value": " ", 6 | "ArrayExpression": 1, 7 | "BinaryExpression": 0, 8 | "ChainedMemberExpression": 0, 9 | "ConditionalExpression": 1, 10 | "MultipleVariableDeclaration": 1, 11 | "ObjectExpression": 1, 12 | "SwitchCase": 1, 13 | "SwitchStatement": 1, 14 | "CatchClause": 1, 15 | "DoWhileStatement": 1, 16 | "ForInStatement": 1, 17 | "ForStatement": 1, 18 | "FunctionDeclaration": 1, 19 | "FunctionExpression": 1, 20 | "IfStatement": 1, 21 | "IfStatementConditional": 1, 22 | "TryStatement": 1, 23 | "WhileStatement": 1, 24 | "TopLevelFunctionBlock": 1 25 | }, 26 | 27 | "lineBreak": { 28 | "value": "\n", 29 | 30 | "before": { 31 | "AssignmentExpression": ">=1", 32 | "AssignmentOperator": 0, 33 | "BlockStatement": 0, 34 | "CallExpression": -1, 35 | "ConditionalExpression": ">=1", 36 | "CatchOpeningBrace": 0, 37 | "CatchClosingBrace": ">=1", 38 | "DeleteOperator": ">=1", 39 | "DoWhileStatement": ">=1", 40 | "DoWhileStatementOpeningBrace": 0, 41 | "DoWhileStatementClosingBrace": ">=1", 42 | "EmptyStatement": -1, 43 | "FinallyOpeningBrace": 0, 44 | "FinallyClosingBrace": ">=1", 45 | "ForInStatement": ">=1", 46 | "ForInStatementExpressionOpening": 0, 47 | "ForInStatementExpressionClosing": 0, 48 | "ForInStatementOpeningBrace": 0, 49 | "ForInStatementClosingBrace": ">=1", 50 | "ForStatement": ">=1", 51 | "ForStatementExpressionOpening": 0, 52 | "ForStatementExpressionClosing": "<2", 53 | "ForStatementOpeningBrace": 0, 54 | "ForStatementClosingBrace": ">=1", 55 | "FunctionExpression": 0, 56 | "FunctionExpressionOpeningBrace": 0, 57 | "FunctionExpressionClosingBrace": ">=1", 58 | "FunctionDeclaration": ">=1", 59 | "FunctionDeclarationOpeningBrace": 0, 60 | "FunctionDeclarationClosingBrace": ">=1", 61 | "IfStatement": ">=1", 62 | "IfStatementOpeningBrace": 0, 63 | "IfStatementClosingBrace": ">=1", 64 | "ElseIfStatement": 0, 65 | "ElseIfStatementOpeningBrace": 0, 66 | "ElseIfStatementClosingBrace": ">=1", 67 | "ElseStatement": 0, 68 | "ElseStatementOpeningBrace": 0, 69 | "ElseStatementClosingBrace": ">=1", 70 | "LogicalExpression": -1, 71 | "ObjectExpressionClosingBrace": ">=1", 72 | "Property": ">=1", 73 | "ReturnStatement": -1, 74 | "SwitchOpeningBrace": 0, 75 | "SwitchClosingBrace": ">=1", 76 | "ThisExpression": -1, 77 | "ThrowStatement": ">=1", 78 | "TryOpeningBrace": 0, 79 | "TryClosingBrace": ">=1", 80 | "VariableName": ">=1", 81 | "VariableValue": 0, 82 | "VariableDeclaration": ">=1", 83 | "VariableDeclarationWithoutInit": ">=1", 84 | "WhileStatement": ">=1", 85 | "WhileStatementOpeningBrace": 0, 86 | "WhileStatementClosingBrace": ">=1" 87 | }, 88 | 89 | "after": { 90 | "AssignmentExpression": ">=1", 91 | "AssignmentOperator": 0, 92 | "BlockStatement": 0, 93 | "CallExpression": -1, 94 | "CatchOpeningBrace": ">=1", 95 | "CatchClosingBrace": ">=0", 96 | "ConditionalExpression": ">=1", 97 | "DeleteOperator": ">=1", 98 | "DoWhileStatement": ">=1", 99 | "DoWhileStatementOpeningBrace": ">=1", 100 | "DoWhileStatementClosingBrace": 0, 101 | "EmptyStatement": -1, 102 | "FinallyOpeningBrace": ">=1", 103 | "FinallyClosingBrace": ">=1", 104 | "ForInStatement": ">=1", 105 | "ForInStatementExpressionOpening": "<2", 106 | "ForInStatementExpressionClosing": -1, 107 | "ForInStatementOpeningBrace": ">=1", 108 | "ForInStatementClosingBrace": ">=1", 109 | "ForStatement": ">=1", 110 | "ForStatementExpressionOpening": "<2", 111 | "ForStatementExpressionClosing": -1, 112 | "ForStatementOpeningBrace": ">=1", 113 | "ForStatementClosingBrace": ">=1", 114 | "FunctionExpression": ">=1", 115 | "FunctionExpressionOpeningBrace": ">=1", 116 | "FunctionExpressionClosingBrace": -1, 117 | "FunctionDeclaration": ">=1", 118 | "FunctionDeclarationOpeningBrace": ">=1", 119 | "FunctionDeclarationClosingBrace": ">=1", 120 | "IfStatement": ">=1", 121 | "IfStatementOpeningBrace": ">=1", 122 | "IfStatementClosingBrace": ">=1", 123 | "ElseIfStatement": ">=1", 124 | "ElseIfStatementOpeningBrace": ">=1", 125 | "ElseIfStatementClosingBrace": ">=1", 126 | "ElseStatement": ">=1", 127 | "ElseStatementOpeningBrace": ">=1", 128 | "ElseStatementClosingBrace": ">=1", 129 | "LogicalExpression": -1, 130 | "ObjectExpressionOpeningBrace": ">=1", 131 | "Property": 0, 132 | "ReturnStatement": -1, 133 | "SwitchOpeningBrace": ">=1", 134 | "SwitchClosingBrace": ">=1", 135 | "ThisExpression": 0, 136 | "ThrowStatement": ">=1", 137 | "TryOpeningBrace": ">=1", 138 | "TryClosingBrace": 0, 139 | "VariableDeclaration": ">=1", 140 | "WhileStatement": ">=1", 141 | "WhileStatementOpeningBrace": ">=1", 142 | "WhileStatementClosingBrace": ">=1" 143 | } 144 | }, 145 | 146 | "whiteSpace": { 147 | "value": " ", 148 | "removeTrailing": 1, 149 | 150 | "before": { 151 | "ArrayExpressionOpening": 0, 152 | "ArrayExpressionClosing": 0, 153 | "ArrayExpressionComma": 0, 154 | "ArgumentComma": 0, 155 | "ArgumentList": 0, 156 | "ArgumentListArrayExpression": 0, 157 | "ArgumentListFunctionExpression": 0, 158 | "ArgumentListObjectExpression": 0, 159 | "AssignmentOperator": 1, 160 | "BinaryExpression": 0, 161 | "BinaryExpressionOperator": 1, 162 | "BlockComment": 1, 163 | "CallExpression": -1, 164 | "CatchParameterList": 1, 165 | "CatchOpeningBrace": 1, 166 | "CatchClosingBrace": 1, 167 | "CommaOperator": 0, 168 | "ConditionalExpressionConsequent": 1, 169 | "ConditionalExpressionAlternate": 1, 170 | "DoWhileStatementOpeningBrace": 1, 171 | "DoWhileStatementClosingBrace": 1, 172 | "DoWhileStatementConditional": 1, 173 | "EmptyStatement": 0, 174 | "ExpressionClosingParentheses": 0, 175 | "FinallyOpeningBrace": 1, 176 | "FinallyClosingBrace": 1, 177 | "ForInStatement": 1, 178 | "ForInStatementExpressionOpening": 1, 179 | "ForInStatementExpressionClosing": 0, 180 | "ForInStatementOpeningBrace": 1, 181 | "ForInStatementClosingBrace": 1, 182 | "ForStatement": 1, 183 | "ForStatementExpressionOpening": 1, 184 | "ForStatementExpressionClosing": 0, 185 | "ForStatementOpeningBrace": 1, 186 | "ForStatementClosingBrace": 1, 187 | "ForStatementSemicolon": 0, 188 | "FunctionDeclarationOpeningBrace": 1, 189 | "FunctionDeclarationClosingBrace": 1, 190 | "FunctionExpressionOpeningBrace": 1, 191 | "FunctionExpressionClosingBrace": 1, 192 | "IfStatementConditionalOpening": 1, 193 | "IfStatementConditionalClosing": 0, 194 | "IfStatementOpeningBrace": 1, 195 | "IfStatementClosingBrace": 1, 196 | "ElseStatementOpeningBrace": 1, 197 | "ElseStatementClosingBrace": 1, 198 | "ElseIfStatementOpeningBrace": 1, 199 | "ElseIfStatementClosingBrace": 1, 200 | "MemberExpressionClosing": 0, 201 | "LineComment": 1, 202 | "LogicalExpressionOperator": 1, 203 | "Property": 1, 204 | "PropertyValue": 1, 205 | "ParameterComma": 0, 206 | "ParameterList": 0, 207 | "SwitchDiscriminantOpening": 1, 208 | "SwitchDiscriminantClosing": 0, 209 | "ThrowKeyword": 1, 210 | "TryOpeningBrace": 1, 211 | "TryClosingBrace": 1, 212 | "UnaryExpressionOperator": 0, 213 | "VariableName": 1, 214 | "VariableValue": 1, 215 | "WhileStatementConditionalOpening": 1, 216 | "WhileStatementConditionalClosing": 0, 217 | "WhileStatementOpeningBrace": 1, 218 | "WhileStatementClosingBrace": 1 219 | }, 220 | 221 | "after": { 222 | "ArrayExpressionOpening": 0, 223 | "ArrayExpressionClosing": 0, 224 | "ArrayExpressionComma": 1, 225 | "ArgumentComma": 1, 226 | "ArgumentList": 0, 227 | "ArgumentListArrayExpression": 0, 228 | "ArgumentListFunctionExpression": 0, 229 | "ArgumentListObjectExpression": 0, 230 | "AssignmentOperator": 1, 231 | "BinaryExpression": 0, 232 | "BinaryExpressionOperator": 1, 233 | "BlockComment": 1, 234 | "CallExpression": 0, 235 | "CatchParameterList": 1, 236 | "CatchOpeningBrace": 1, 237 | "CatchClosingBrace": 1, 238 | "CommaOperator": 1, 239 | "ConditionalExpressionConsequent": 1, 240 | "ConditionalExpressionTest": 1, 241 | "DoWhileStatementOpeningBrace": 1, 242 | "DoWhileStatementClosingBrace": 1, 243 | "DoWhileStatementBody": 1, 244 | "EmptyStatement": 0, 245 | "ExpressionOpeningParentheses": 0, 246 | "FinallyOpeningBrace": 1, 247 | "FinallyClosingBrace": 1, 248 | "ForInStatement": 1, 249 | "ForInStatementExpressionOpening": 0, 250 | "ForInStatementExpressionClosing": 1, 251 | "ForInStatementOpeningBrace": 1, 252 | "ForInStatementClosingBrace": 1, 253 | "ForStatement": 1, 254 | "ForStatementExpressionOpening": 0, 255 | "ForStatementExpressionClosing": 1, 256 | "ForStatementClosingBrace": 1, 257 | "ForStatementOpeningBrace": 1, 258 | "ForStatementSemicolon": 1, 259 | "FunctionReservedWord": 0, 260 | "FunctionName": 0, 261 | "FunctionExpressionOpeningBrace": 1, 262 | "FunctionExpressionClosingBrace": 0, 263 | "FunctionDeclarationOpeningBrace": 1, 264 | "FunctionDeclarationClosingBrace": 1, 265 | "IfStatementConditionalOpening": 0, 266 | "IfStatementConditionalClosing": 1, 267 | "IfStatementOpeningBrace": 1, 268 | "IfStatementClosingBrace": 1, 269 | "ElseStatementOpeningBrace": 1, 270 | "ElseStatementClosingBrace": 1, 271 | "ElseIfStatementOpeningBrace": 1, 272 | "ElseIfStatementClosingBrace": 1, 273 | "MemberExpressionOpening": 0, 274 | "LogicalExpressionOperator": 1, 275 | "ObjectExpressionClosingBrace": 0, 276 | "PropertyName": 0, 277 | "PropertyValue": 0, 278 | "ParameterComma": 1, 279 | "ParameterList": 0, 280 | "SwitchDiscriminantOpening": 0, 281 | "SwitchDiscriminantClosing": 1, 282 | "ThrowKeyword": 1, 283 | "TryOpeningBrace": 1, 284 | "TryClosingBrace": 1, 285 | "UnaryExpressionOperator": 0, 286 | "VariableName": 1, 287 | "WhileStatementConditionalOpening": 0, 288 | "WhileStatementConditionalClosing": 1, 289 | "WhileStatementOpeningBrace": 1, 290 | "WhileStatementClosingBrace": 1 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /lib/format.js: -------------------------------------------------------------------------------- 1 | var esformatter = require('esformatter'); 2 | 3 | var config = require('./config.js'); 4 | var helpers = require('./helpers'); 5 | 6 | exports.format = function(js, options) { 7 | options = options || config.getConfig(); 8 | 9 | // esformatter doesn't like shebangs 10 | // remove if one exists as the first line 11 | var sheBang = helpers.getShebangLine(js); 12 | if (sheBang) { 13 | js = js.substring(sheBang.length); 14 | } 15 | js = esformatter.format(js, options); 16 | 17 | // if we had a shebang, add back in 18 | if (sheBang) { 19 | js = sheBang + js; 20 | } 21 | 22 | return js; 23 | }; 24 | 25 | exports.formatJSON = function(json, options) { 26 | json = 'var data = ' + json; 27 | json = exports.format(json, options); 28 | return json.substring(json.indexOf('=') + 2); 29 | }; 30 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * getShebangLine 3 | * returns a string of the shebang line if it exists, otherwise null 4 | * 5 | * @param str 6 | * @return {String|Null} 7 | */ 8 | var getShebangLine = function(str) { 9 | var match = /^#!.*\n/.exec(str); 10 | //return first line match or null 11 | return match && match[0] || null; 12 | }; 13 | 14 | /** 15 | * removeShebang 16 | * removes shebang line from string if it exists and returns it 17 | * 18 | * @param str 19 | * @return {String} 20 | */ 21 | var removeShebang = function(str) { 22 | var sheBang = getShebangLine(str); 23 | return sheBang ? str.substring(sheBang.length) : str; 24 | }; 25 | 26 | exports.removeShebang = removeShebang; 27 | exports.getShebangLine = getShebangLine; 28 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var rewritePath = './rewrite.js'; 2 | var format = require('./format.js'); 3 | var validate = require('./validate.js'); 4 | var ast = require('./ast.js'); 5 | 6 | exports.rewrite = require(rewritePath).rewrite; 7 | exports.search = require(rewritePath).search; 8 | exports.format = format.format; 9 | exports.formatJSON = format.formatJSON; 10 | exports.validate = validate.validate; 11 | exports.validateJSON = validate.validateJSON; 12 | exports.getConfig = require('./config.js').getConfig; 13 | exports.parseAST = ast.parseAST; 14 | exports.generateAST = ast.generateAST; 15 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | var falafel = require('falafel'); 2 | var esprima = require('esprima'); 3 | 4 | module.exports = { 5 | walk: function(js, callback) { 6 | return falafel(js, { 7 | parser: this 8 | }, function(node) { 9 | // Defining the start and end is required for compatibility with falafels 'update' 10 | node.start = node.range[0]; 11 | node.end = node.range[1]; 12 | return callback(node); 13 | }); 14 | }, 15 | parse: function(js) { 16 | return esprima.parse(js, { 17 | sourceType: 'module', 18 | raw: true, 19 | range: true, 20 | loc: true 21 | }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /lib/rewrite.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var escodegen = require('escodegen'); 3 | var _ = require('underscore'); 4 | var helpers = require('./helpers'); 5 | var parser = require('./parser'); 6 | 7 | // Used to exclude circular references and functions 8 | function deepomit(obj, keys) { 9 | if (util.isArray(obj)) { 10 | return obj.map(function(val) { 11 | return deepomit(val, keys); 12 | }); 13 | } else if (_.isObject(obj)) { 14 | var filtered = _.omit.apply(_, [obj].concat(keys)); 15 | for (var key in filtered) { 16 | filtered[key] = deepomit(filtered[key], keys); 17 | } 18 | return filtered; 19 | } else { 20 | return obj; 21 | } 22 | } 23 | 24 | var cloneIgnoredKeys = ['parent', 'update', 'source']; 25 | var matchIgnoredKeys = ['type', 'prefix', 'sourceType', 'loc', 'raw', 'range']; 26 | 27 | function clone(obj) { 28 | return JSON.parse(JSON.stringify(deepomit(obj, cloneIgnoredKeys))); 29 | } 30 | 31 | function isWildcard(node) { 32 | return node.type == "Identifier" && /^[a-z]$/.test(node.name); 33 | } 34 | 35 | function isSpreadWildcard(node) { 36 | return node.type == "SpreadElement" && isWildcard(node.argument); 37 | } 38 | 39 | // Exposes the "meat" of the node 40 | function unwrapNode(node) { 41 | 42 | // If the program is only one expression/statement 43 | // assume we want to match the contents. 44 | if (node.type == 'Program' && node.body.length == 1) { 45 | node = unwrapNode(node.body[0]); 46 | 47 | // We want to match the expression, not the statement. 48 | } else if (node.type == 'ExpressionStatement') { 49 | node = unwrapNode(node.expression); 50 | } 51 | 52 | return node; 53 | } 54 | 55 | // Same as `unwrapNode` except that this ensures both nodes are in sync 56 | function unwrapNodes(a, b) { 57 | 58 | if (a.type == 'Program' && a.body.length == 1 && b.type == 'Program' && b.body.length == 1) { 59 | return unwrapNodes(a.body[0], b.body[0]); 60 | 61 | } else if (a.type == 'ExpressionStatement' && b.type == 'ExpressionStatement') { 62 | return unwrapNodes(a.expression, b.expression); 63 | } 64 | 65 | return [a, b]; 66 | } 67 | 68 | function matchPartial(wildcards, patterns, nodes) { 69 | // Copy nodes so we don't affect the original. 70 | nodes = nodes.slice(); 71 | 72 | // Account for rest param by slicing off nodes and 73 | // placing them in the wildcards index 74 | var rest = _.last(patterns); 75 | if (rest && isSpreadWildcard(rest)) { 76 | wildcards['...' + rest.argument.name] = _.rest(nodes, patterns.length - 1); 77 | patterns.pop(); // Remove rest param 78 | } 79 | 80 | // Given an array of patterns, are each satisfied by 81 | // a unique node in the array of nodes. 82 | return _.all(patterns, function(pattern) { 83 | var index = -1; 84 | 85 | // Using _.any, instead of _.reject since it breaks 86 | // iteration on the first truthy result. 87 | _.any(nodes, function(node, i) { 88 | if (matchNode(wildcards, pattern, node)) { 89 | index = i; 90 | return true; 91 | } else { 92 | return false; 93 | } 94 | }); 95 | 96 | if (index > -1) { 97 | // Remove the node so we don't consider it again and 98 | // fulfill a different wildcard. 99 | nodes.splice(index, 1); 100 | return true; 101 | } else { 102 | return false; 103 | } 104 | }); 105 | } 106 | 107 | function isComparable(patternType, nodeType) { 108 | if (patternType == nodeType) { 109 | return true; 110 | } 111 | 112 | if (patternType == 'BlockStatement' && nodeType == 'Program') { 113 | return true; 114 | } 115 | 116 | if (patternType == 'Program' && nodeType == 'BlockStatement') { 117 | return true; 118 | } 119 | 120 | return false; 121 | } 122 | 123 | function matchNode(wildcards, pattern, node) { 124 | if (pattern === null && node !== null) { 125 | return false; 126 | } 127 | 128 | if (pattern !== null && node === null) { 129 | return false; 130 | } 131 | 132 | if (pattern === null && node === null) { 133 | return true; 134 | } 135 | 136 | if (wildcards !== null && isWildcard(pattern)) { 137 | if (pattern.name in wildcards) { 138 | return matchNode(null, wildcards[pattern.name], node); 139 | } 140 | wildcards[pattern.name] = node; 141 | return true; 142 | } 143 | 144 | if (!isComparable(pattern.type, node.type)) { 145 | return false; 146 | } 147 | 148 | var rest = _.last(pattern.params); 149 | if (rest && rest.type === 'RestElement') { 150 | pattern.params.pop(); 151 | wildcards['...' + rest.argument.name] = node.params; 152 | } 153 | 154 | for (var key in pattern) { 155 | 156 | // Ignore some node properties 157 | if (_.contains(cloneIgnoredKeys.concat(matchIgnoredKeys), key)) { 158 | continue; 159 | } 160 | 161 | // Match array property 162 | if (_.isArray(pattern[key])) { 163 | if (!matchPartial(wildcards, pattern[key], node[key])) { 164 | return false; 165 | } 166 | 167 | // Match object property 168 | } else if (_.isObject(pattern[key])) { 169 | 170 | // Special case rest params (requires knowledge of sibling nodes) 171 | if (key == 'rest' && pattern.rest && node.params && isWildcard(pattern.rest)) { 172 | wildcards['...' + pattern.rest.name] = node.params; 173 | 174 | } else if (pattern[key] && node[key] && !matchNode(wildcards, pattern[key], node[key])) { 175 | return false; 176 | } 177 | 178 | // Match other properties (string, boolean, null, etc.) 179 | } else if (pattern[key] !== node[key]) { 180 | return false; 181 | } 182 | } 183 | 184 | return true; 185 | } 186 | 187 | function rewritePartial(wildcards, replacements) { 188 | return replacements.map(function(replacement) { 189 | return rewriteNode(wildcards, replacement); 190 | }); 191 | } 192 | 193 | // `rewriteNode` replaces wildcards with matched wildcard values 194 | function rewriteNode(wildcards, replacement, node) { 195 | node = node || {}; 196 | 197 | // Handle wildcards 198 | if (isWildcard(replacement) && replacement.name in wildcards) { 199 | replacement = wildcards[replacement.name]; 200 | 201 | } else { 202 | 203 | // Handle other properties 204 | for (var key in replacement) { 205 | if (_.contains(cloneIgnoredKeys.concat(matchIgnoredKeys), key)) { 206 | continue; 207 | } 208 | 209 | if (_.isArray(replacement[key])) { 210 | replacement[key] = rewritePartial(wildcards, replacement[key]); 211 | } else if (_.isObject(replacement[key])) { 212 | replacement[key] = rewriteNode(wildcards, replacement[key], node[key]); 213 | } 214 | } 215 | 216 | // Unpack rest param from wildcards 217 | if (_.contains(['FunctionExpression', 'FunctionDeclaration'], replacement.type)) { 218 | var rest = _.last(replacement.params); 219 | if (rest && rest.type === 'RestElement' && isWildcard(rest.argument)) { 220 | replacement.params.pop(); 221 | replacement.params = replacement.params.concat(wildcards['...' + rest.argument.name]); 222 | } 223 | } else if (replacement.type == 'CallExpression') { 224 | var spread = _.last(replacement.arguments); 225 | if (spread && isSpreadWildcard(spread)) { 226 | replacement.arguments.pop(); 227 | replacement.arguments = replacement.arguments.concat(wildcards['...' + spread.argument.name]); 228 | } 229 | } 230 | } 231 | 232 | return replacement; 233 | } 234 | 235 | exports.rewrite = function(js, rewriteRule) { 236 | 237 | var sheBang = helpers.getShebangLine(js); 238 | // esformatter doesn't like shebangs 239 | // remove if one exists as the first line 240 | if (sheBang) { 241 | js = js.substring(sheBang.length); 242 | } 243 | 244 | var rewriteRuleRe = /\s*->\s*/g; 245 | if (!rewriteRuleRe.test(rewriteRule)) { 246 | return js; 247 | } 248 | 249 | var rewriteRuleParts = rewriteRule.split(rewriteRuleRe); 250 | if (rewriteRuleParts.length != 2) { 251 | return js; 252 | } 253 | 254 | var pattern = parser.parse(rewriteRuleParts[0]); 255 | var replacement = parser.parse(rewriteRuleParts[1]); 256 | 257 | var patternReplacement = unwrapNodes(pattern, replacement); 258 | pattern = patternReplacement[0]; 259 | replacement = patternReplacement[1]; 260 | 261 | js = parser.walk(js, function(node) { 262 | var wildcards = {}; 263 | if (matchNode(wildcards, pattern, node)) { 264 | var clonedReplacement = clone(replacement); 265 | 266 | // Set replacement node type to match original node type. This is to 267 | // account for cases when two nodes are comparable but not equal. 268 | if (isComparable(clonedReplacement.type, node.type)) { 269 | clonedReplacement.type = node.type; 270 | } 271 | 272 | var clonedNode = clone(node); 273 | var updatedNode = rewriteNode(wildcards, clonedReplacement, clonedNode); 274 | node.update(escodegen.generate(updatedNode)); 275 | } 276 | }); 277 | 278 | // if we had a shebang, add back in 279 | if (sheBang) { 280 | js = sheBang + js; 281 | } 282 | 283 | return js; 284 | }; 285 | 286 | exports.search = function(js, searchRule) { 287 | // esformatter doesn't like shebangs 288 | // remove if one exists as the first line 289 | js = helpers.removeShebang(js); 290 | 291 | var pattern = unwrapNode(parser.parse(searchRule)); 292 | 293 | var matches = []; 294 | parser.walk(js, function(node) { 295 | var wildcards = {}; 296 | if (matchNode(wildcards, pattern, node)) { 297 | matches.push({ 298 | node: node, 299 | wildcards: wildcards 300 | }); 301 | } 302 | }); 303 | return matches; 304 | }; 305 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var util = require('util'); 3 | var path = require('path'); 4 | var child_process = require('child_process'); 5 | 6 | var glob = require('glob'); 7 | 8 | var docopt = require('docopt'); 9 | var _ = require('underscore'); 10 | 11 | var jsfmt = require('./index'); 12 | 13 | var tmp = require('tmp'); 14 | tmp.setGracefulCleanup(); 15 | 16 | var doc = [ 17 | 'Usage:', 18 | ' jsfmt [--no-format] [--save-ast] [--diff|--list|--write] [--validate] [--rewrite PATTERN|--search PATTERN] [--json|--ast] [...]', 19 | ' jsfmt (--version | --help)', 20 | '', 21 | 'Options:', 22 | ' -h --help Show this help text', 23 | ' --version Show jsfmt version', 24 | ' -d --diff Show diff against original file', 25 | ' -l --list List the files which differ from jsfmt output', 26 | ' -v --validate Validate the input file(s)', 27 | ' --no-format Do not format the input file(s)', 28 | ' -w --write Overwrite the original file with jsfmt output', 29 | ' -j --json Tell jsfmt that the file being parsed is json', 30 | ' -a --ast Tell jsfmt that the file being parsed is in JSON AST', 31 | ' --save-ast Output the resulting js in JSON AST format', 32 | ' -r=PATTERN --rewrite PATTERN Rewrite rule (e.g., \'a.slice(b, len(a) -> a.slice(b)\')', 33 | ' -s=PATTERN --search PATTERN Search rule (e.g., \'a.slice\')', 34 | ].join("\r\n"); 35 | 36 | var info = require('../package.json'); 37 | var argv = docopt.docopt(doc, { 38 | help: true, 39 | version: 'jsfmt ' + info.version, 40 | }); 41 | 42 | if (argv['--json'] && (argv['--rewrite'] || argv['--search'])) { 43 | console.error('Rewriting/Searching is not supported for JSON'); 44 | process.exit(-1); 45 | } 46 | 47 | function diff(pathA, pathB, callback) { 48 | child_process.exec([ 49 | 'git', 'diff', '--ignore-space-at-eol', '--no-index', '--', pathA, pathB 50 | ].join(' '), callback); 51 | } 52 | 53 | function handleDiff(fullPath, originalJavascript, formattedJavascript) { 54 | if (fullPath == 'stdin') { 55 | tmp.file(function(err, pathA, fdA) { 56 | if (err) { 57 | console.error(err); 58 | return; 59 | } 60 | fs.writeSync(fdA, originalJavascript); 61 | 62 | tmp.file(function(err, pathB, fdB) { 63 | if (err) { 64 | console.error(err); 65 | return; 66 | } 67 | fs.writeSync(fdB, formattedJavascript); 68 | 69 | diff(pathA, pathB, function(err, stdout, stderr) { 70 | if (stdout) { 71 | console.log(stdout); 72 | } 73 | if (stderr) { 74 | console.log(stderr); 75 | } 76 | }); 77 | }); 78 | }); 79 | } else { 80 | tmp.file(function(err, pathA, fdA) { 81 | if (err) { 82 | console.error(err); 83 | return; 84 | } 85 | fs.writeSync(fdA, formattedJavascript); 86 | 87 | diff(fullPath, pathA, function(err, stdout, stderr) { 88 | if (stdout) { 89 | console.log(stdout); 90 | } 91 | if (stderr) { 92 | console.error(stderr); 93 | } 94 | }); 95 | }); 96 | } 97 | } 98 | 99 | function handleJavascript(fullPath, original) { 100 | var js = original; 101 | var relativePath = path.relative(process.cwd(), fullPath); 102 | 103 | if (argv['--ast']) { 104 | try { 105 | js = jsfmt.parseAST(JSON.parse(js)); 106 | } catch ( err ) { 107 | console.error(relativePath, err.message); 108 | return false; 109 | } 110 | } 111 | 112 | if (argv['--search']) { 113 | try { 114 | jsfmt.search(js, argv['--search']).forEach(function(match) { 115 | var node = match.node; 116 | var loc = node.loc; 117 | var startLine = loc.start.line; 118 | var endLine = loc.end.line; 119 | console.log([relativePath, _.uniq([startLine, endLine]).join(':')].join(':')); 120 | 121 | var partialJavascript = js.split('\n').slice(startLine - 1, endLine).join('\n'); 122 | console.log(partialJavascript, '\n'); 123 | }); 124 | } catch ( err ) { 125 | console.error(relativePath, err.message); 126 | return false; 127 | } 128 | return true; 129 | } 130 | 131 | if (argv['--rewrite']) { 132 | try { 133 | js = jsfmt.rewrite(js, argv['--rewrite']).toString(); 134 | } catch ( err ) { 135 | console.error(relativePath, err); 136 | return false; 137 | } 138 | } 139 | 140 | if (!argv['--no-format']) { 141 | try { 142 | if (argv['--json']) { 143 | js = jsfmt.formatJSON(js); 144 | } else { 145 | js = jsfmt.format(js); 146 | } 147 | } catch ( err ) { 148 | console.error(relativePath, err); 149 | return false; 150 | } 151 | } 152 | 153 | if (argv['--validate']) { 154 | var errors = null; 155 | if (argv['--json']) { 156 | errors = jsfmt.validateJSON(js); 157 | } else { 158 | errors = jsfmt.validate(js); 159 | } 160 | if (errors && errors.length) { 161 | errors.forEach(function(error) { 162 | var msg = util.format('Error: %s Line: %s Column: %s', error.description, error.lineNumber, error.column); 163 | console.error(msg); 164 | }); 165 | return false; 166 | } 167 | } 168 | 169 | if (argv['--diff']) { 170 | handleDiff(fullPath, original, js); 171 | } else if (argv['--list']) { 172 | // Print filenames who differ 173 | if (original != js) { 174 | console.log(relativePath); 175 | } 176 | } else if (argv['--write']) { 177 | // Overwrite original file 178 | fs.writeFileSync(fullPath, js); 179 | } else { 180 | if (argv['--save-ast']) { 181 | var ast = jsfmt.generateAST(js); 182 | js = JSON.stringify(ast); 183 | if (!argv['--no-format']) { 184 | js = jsfmt.formatJSON(js); 185 | } 186 | } 187 | // Print to stdout 188 | process.stdout.write(js); 189 | } 190 | return true; 191 | } 192 | 193 | function handleDirectory(currentPath, callback) { 194 | child_process.execFile('find', [currentPath, '-name', '*.js'], function(err, stdout, stderr) { 195 | var paths = _.filter(stdout.split('\n').slice(0, -1), function(currentPath) { 196 | return path.basename(currentPath).indexOf('.') !== 0; // Remove hidden files 197 | }); 198 | callback(paths); 199 | }); 200 | } 201 | 202 | var paths = argv['']; 203 | 204 | if (paths.length > 0) { 205 | paths.forEach(function(currentPath) { 206 | 207 | // Unpack globs (e.g. "**/*.js") 208 | glob(currentPath, function(err, paths) { 209 | if (err) { 210 | console.error(err); 211 | return; 212 | } 213 | 214 | paths.forEach(function(currentPath) { 215 | var fullPath = path.resolve(process.cwd(), currentPath); 216 | if (fs.statSync(fullPath).isDirectory()) { 217 | handleDirectory(fullPath, function(paths) { 218 | _.each(paths, function(fullPath) { 219 | if (!handleJavascript(path.normalize(fullPath), fs.readFileSync(fullPath, 'utf-8'))) { 220 | process.exitCode = -1; 221 | } 222 | }); 223 | }); 224 | } else { 225 | if (!handleJavascript(fullPath, fs.readFileSync(fullPath, 'utf-8'))) { 226 | process.exitCode = -1; 227 | } 228 | } 229 | }); 230 | }); 231 | }); 232 | } else { 233 | var js = ''; 234 | process.stdin.setEncoding('utf8'); 235 | process.stdin.on('readable', function() { 236 | var chunk = process.stdin.read(); 237 | if (chunk !== null) { 238 | js += chunk; 239 | } else if (chunk === null && js === '') { 240 | console.log(doc); 241 | process.exit(-1); 242 | } 243 | }); 244 | process.stdin.on('end', function() { 245 | if (!handleJavascript('stdin', js)) { 246 | process.exitCode = -1; 247 | } 248 | }); 249 | } 250 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | var esprima = require('esprima'); 2 | var helpers = require('./helpers'); 3 | 4 | module.exports.validate = function(js) { 5 | // esformatter doesn't like shebangs 6 | // remove if one exists as the first line 7 | js = helpers.removeShebang(js); 8 | 9 | var syntax = esprima.parse(js, { 10 | tolerant: true 11 | }); 12 | return syntax.errors; 13 | }; 14 | 15 | module.exports.validateJSON = function(json) { 16 | json = 'var data = ' + json; 17 | return module.exports.validate(json); 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsfmt", 3 | "description": "gofmt for javascript", 4 | "version": "0.5.3", 5 | "homepage": "https://github.com/rdio/jsfmt", 6 | "main": "./lib/index.js", 7 | "engines": { 8 | "node": ">=0.4.0" 9 | }, 10 | "bin": { 11 | "jsfmt": "./bin/jsfmt" 12 | }, 13 | "scripts": { 14 | "test": "grunt && grunt fmt && grunt verify", 15 | "fmt": "grunt fmt" 16 | }, 17 | "files": [ 18 | "bin", 19 | "lib", 20 | "LICENSE.MD" 21 | ], 22 | "maintainers": [ 23 | { 24 | "name": "Jim Fleming", 25 | "email": "jim@barkingmousestudio.com" 26 | } 27 | ], 28 | "dependencies": { 29 | "deep-extend": "~0.4.0", 30 | "docopt": "~0.4.1", 31 | "escodegen": "~1.7.0", 32 | "esformatter": "~0.9.0", 33 | "esformatter-braces": "~1.2.1", 34 | "esformatter-var-each": "~2.1.0", 35 | "esprima": "~2.7.0", 36 | "falafel": "~1.2.0", 37 | "glob": "~5.0.15", 38 | "rc": "~1.1.2", 39 | "rocambole": "~0.7.0", 40 | "tmp": "~0.0.23", 41 | "underscore": "~1.8.0" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "https://github.com/rdio/jsfmt" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/rdio/jsfmt/issues" 49 | }, 50 | "licenses": [ 51 | { 52 | "type": "Apache2.0", 53 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 54 | } 55 | ], 56 | "keywords": [ 57 | "ast", 58 | "javascript", 59 | "parser", 60 | "rewrite", 61 | "syntax", 62 | "format", 63 | "search" 64 | ], 65 | "devDependencies": { 66 | "coveralls": "~2.11.4", 67 | "grunt": "~0.4.5", 68 | "grunt-cli": "~0.1.13", 69 | "grunt-contrib-jshint": "~0.11.3", 70 | "grunt-exec": "~0.4.5", 71 | "grunt-mocha-test": "~0.12.7", 72 | "jscoverage": "~0.6.0", 73 | "json-stable-stringify": "~1.0.0", 74 | "mocha": "~2.3.3", 75 | "mocha-lcov-reporter": "1.0.0", 76 | "should": "~7.1.1" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/ast.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | /* global describe,it */ 3 | 'use strict'; 4 | var should = require('should'); 5 | 6 | var libPath = process.env.JSFMT_COV ? 'lib-cov' : 'lib'; 7 | var jsfmt = require('../' + libPath + '/index'); 8 | var stringify = require('json-stable-stringify'); 9 | 10 | describe('jsfmt.parseAST', function() { 11 | it('should test basic ast json parsing', function() { 12 | var ast = '{"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"a","range":[4,5]},"init":{"type":"Literal","value":50,"raw":"50","range":[8,10]},"range":[4,10]}],"kind":"var","range":[0,11]},{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Identifier","name":"b","range":[16,17]},"init":{"type":"Literal","value":100,"raw":"100","range":[20,23]},"range":[16,23]}],"kind":"var","range":[12,24]}],"range":[0,24],"comments":[],"tokens":[{"type":"Keyword","value":"var","range":[0,3]},{"type":"Identifier","value":"a","range":[4,5]},{"type":"Punctuator","value":"=","range":[6,7]},{"type":"Numeric","value":"50","range":[8,10]},{"type":"Punctuator","value":";","range":[10,11]},{"type":"Keyword","value":"var","range":[12,15]},{"type":"Identifier","value":"b","range":[16,17]},{"type":"Punctuator","value":"=","range":[18,19]},{"type":"Numeric","value":"100","range":[20,23]},{"type":"Punctuator","value":";","range":[23,24]}]}'; 13 | ast = JSON.parse(ast); 14 | 15 | var js = jsfmt.parseAST(ast); 16 | js.should.eql('var a = 50;\nvar b = 100;'); 17 | }); 18 | }); 19 | 20 | describe('jsfmt.generateAST', function() { 21 | it('should test basic ast generation', function() { 22 | var js = 'var a = 50;\nvar b = 100;'; 23 | var ast = jsfmt.generateAST(js); 24 | 25 | var astExpected = JSON.parse('{"body":[{"declarations":[{"id":{"name":"a","range":[4,5],"type":"Identifier"},"init":{"range":[8,10],"raw":"50","type":"Literal","value":50},"range":[4,10],"type":"VariableDeclarator"}],"kind":"var","range":[0,11],"type":"VariableDeclaration"},{"declarations":[{"id":{"name":"b","range":[16,17],"type":"Identifier"},"init":{"range":[20,23],"raw":"100","type":"Literal","value":100},"range":[16,23],"type":"VariableDeclarator"}],"kind":"var","range":[12,24],"type":"VariableDeclaration"}],"comments":[],"range":[0,24],"sourceType":"module","tokens":[{"range":[0,3],"type":"Keyword","value":"var"},{"range":[4,5],"type":"Identifier","value":"a"},{"range":[6,7],"type":"Punctuator","value":"="},{"range":[8,10],"type":"Numeric","value":"50"},{"range":[10,11],"type":"Punctuator","value":";"},{"range":[12,15],"type":"Keyword","value":"var"},{"range":[16,17],"type":"Identifier","value":"b"},{"range":[18,19],"type":"Punctuator","value":"="},{"range":[20,23],"type":"Numeric","value":"100"},{"range":[23,24],"type":"Punctuator","value":";"}],"type":"Program"}'); 26 | 27 | stringify(ast).should.eql(stringify(astExpected)); 28 | }); 29 | 30 | it('should parse es6 ast', function() { 31 | var js = 'import foo from "foo";'; 32 | var ast = jsfmt.generateAST(js); 33 | 34 | var astExpected = JSON.parse('{"body":[{"range":[0,22],"source":{"range":[16,21],"raw":"\\"foo\\"","type":"Literal","value":"foo"},"specifiers":[{"local":{"name":"foo","range":[7,10],"type":"Identifier"},"range":[7,10],"type":"ImportDefaultSpecifier"}],"type":"ImportDeclaration"}],"comments":[],"range":[0,22],"sourceType":"module","tokens":[{"range":[0,6],"type":"Keyword","value":"import"},{"range":[7,10],"type":"Identifier","value":"foo"},{"range":[11,15],"type":"Identifier","value":"from"},{"range":[16,21],"type":"String","value":"\\"foo\\""},{"range":[21,22],"type":"Punctuator","value":";"}],"type":"Program"}'); 35 | 36 | stringify(ast).should.eql(stringify(astExpected)); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/format.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | /* global describe,it */ 3 | 'use strict'; 4 | var should = require('should'); 5 | var fs = require('fs'); 6 | 7 | var libPath = process.env.JSFMT_COV ? 'lib-cov' : 'lib'; 8 | var jsfmt = require('../' + libPath + '/index'); 9 | 10 | describe('jsfmt.format', function() { 11 | it('should test basic formatting', function() { 12 | var js = 'var func = function(test){console.log( test );};'; 13 | var result = jsfmt.format(js, {}); 14 | result.should.eql('var func = function(test) {\n console.log(test);\n};'); 15 | }); 16 | 17 | it('should test shebangs', function() { 18 | var js = '#!/usr/bin/env node\nvar func = function(test){console.log( test );};'; 19 | var result = jsfmt.format(js, {}); 20 | result.should.eql('#!/usr/bin/env node\nvar func = function(test) {\n console.log(test);\n};'); 21 | }); 22 | 23 | it('should convert a list of var declarations to individual declarations', function() { 24 | var js = 'var a,\n b = 2,\n c = 3;'; 25 | var result = jsfmt.format(js, { 26 | plugins: ['esformatter-var-each'] 27 | }); 28 | result.should.eql('var a;\nvar b = 2;\nvar c = 3;'); 29 | }); 30 | 31 | it('should try/catch blocks properly', function() { 32 | var js = 'try {\nvar foo = \'bar\';\n} catch (err) {\n// ignore\n}'; 33 | var result = jsfmt.format(js, {}); 34 | result.should.eql( 35 | 'try {\n var foo = \'bar\';\n} catch (err) {\n // ignore\n}' 36 | ); 37 | }); 38 | 39 | it('should format es6 imports', function() { 40 | var js = 'import foo from "foo";'; 41 | var result = jsfmt.format(js, {}); 42 | result.should.eql('import foo from "foo";'); 43 | }); 44 | }); 45 | 46 | describe('jsfmt.formatJSON', function() { 47 | it('should test formatting json object', function() { 48 | var json = '{"hello":"world"}'; 49 | var result = jsfmt.formatJSON(json, {}); 50 | result.should.eql('{\n "hello": "world"\n}'); 51 | }); 52 | 53 | it('should test formatting json array', function() { 54 | var json = '["hello","world"]'; 55 | var result = jsfmt.formatJSON(json, {}); 56 | result.should.eql('["hello", "world"]'); 57 | }); 58 | 59 | it('should test formatting json array of objects', function() { 60 | var json = '[{"hello":"world"},{"foo":500.0}]'; 61 | var result = jsfmt.formatJSON(json, {}); 62 | result.should.eql('[{\n "hello": "world"\n}, {\n "foo": 500.0\n}]'); 63 | }); 64 | 65 | it('should correctly format with trailing new line', function() { 66 | var json = '{"a":1,"b":"c"}\n'; 67 | var result = jsfmt.formatJSON(json, {}); 68 | result.should.eql('{\n "a": 1,\n "b": "c"\n}\n'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/rewrite.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | /* global describe,it */ 3 | 'use strict'; 4 | var should = require('should'); 5 | var fs = require('fs'); 6 | 7 | var libPath = process.env.JSFMT_COV ? 'lib-cov' : 'lib'; 8 | var jsfmt = require('../' + libPath + '/index'); 9 | 10 | describe('jsfmt.rewrite', function() { 11 | it('should test basic rewrite', function() { 12 | jsfmt.rewrite('_.each(a, b)', '_.each(a, b) -> a.forEach(b)') 13 | .toString().should.eql('a.forEach(b)'); 14 | 15 | jsfmt.rewrite('_.each(e, f)', '_.each(a, b) -> a.forEach(b)') 16 | .toString().should.eql('e.forEach(f)'); 17 | 18 | jsfmt.rewrite('_.reduce(a,b,c)', '_.reduce(a, b, c) -> a.reduce(b, c)') 19 | .toString().should.eql('a.reduce(b, c)'); 20 | }); 21 | 22 | it('should test basic rewrite with shebang', function() { 23 | jsfmt.rewrite('#!/usr/bin/env node\n_.each(a, b)', '_.each(a, b) -> a.forEach(b)') 24 | .toString().should.eql('#!/usr/bin/env node\na.forEach(b)'); 25 | 26 | jsfmt.rewrite('#!/usr/bin/env node\n_.each(e, f)', '_.each(a, b) -> a.forEach(b)') 27 | .toString().should.eql('#!/usr/bin/env node\ne.forEach(f)'); 28 | 29 | jsfmt.rewrite('#!/usr/bin/env node\n_.reduce(a,b,c)', '_.reduce(a, b, c) -> a.reduce(b, c)') 30 | .toString().should.eql('#!/usr/bin/env node\na.reduce(b, c)'); 31 | }); 32 | 33 | it('should be able to rewrite variable declaration', function() { 34 | jsfmt.rewrite('var myA = 1, myB = 2;', 'noop -> noop') 35 | .toString().should.eql('var myA = 1, myB = 2;'); 36 | 37 | // As "Program" 38 | jsfmt.rewrite('var myA = 1, myB = 2;', 'var a = c, b = d; -> var a = c; var b = d;') 39 | .toString().should.eql('var myA = 1;\nvar myB = 2;'); 40 | 41 | // Inside of "BlockStatement" instead of "Program" 42 | jsfmt.rewrite('function test() { var myA = 1, myB = 2; }', 'var a = c, b = d; -> var a = c; var b = d;') 43 | .toString().should.eql('function test() {\n var myA = 1;\n var myB = 2;\n}'); 44 | }); 45 | 46 | it('should be able to rewrite FunctionDeclaration', function() { 47 | jsfmt.rewrite('function myFunc() { return false; }', 'function a() {} -> function wrapper(a) {}') 48 | .toString().should.eql('function wrapper(myFunc) {\n}'); 49 | }); 50 | 51 | it('should be able to perform a basic rewrite inside a block', function() { 52 | jsfmt.rewrite('function test() { return _.map([0, 1, 2], function(val) { return val * val; }); }', 53 | '_.map(a, b) -> a.map(b)') 54 | .toString().should.eql('function test() { return [\n 0,\n 1,\n 2\n].map(function (val) {\n return val * val;\n}); }'); 55 | }); 56 | 57 | it('should be able to rewrite unary expression', function() { 58 | jsfmt.rewrite('var test = !0;', '!0 -> true').toString().should.eql('var test = true;'); 59 | jsfmt.rewrite('var test = !0;', '!0 -> !1').toString().should.eql('var test = !1;'); 60 | }); 61 | 62 | it('should rewrite AssignmentExpression', function() { 63 | jsfmt.rewrite('var test = 4;', 'var a = b -> a += b').toString().should.eql('test += 4;'); 64 | }); 65 | 66 | it('should support rewriting of es6 code', function() { 67 | var source = 'import "foo"; function foo(foo, bar) {}'; 68 | var replacement = 'function foo(a, b) {} -> function foo(b, a) {}'; 69 | 70 | var rewritten = jsfmt.rewrite(source, replacement).toString(); 71 | 72 | rewritten.should.eql('import "foo"; function foo(bar, foo) {\n}'); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/search.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | /* global describe,it */ 3 | 'use strict'; 4 | var should = require('should'); 5 | var fs = require('fs'); 6 | 7 | var libPath = process.env.JSFMT_COV ? 'lib-cov' : 'lib'; 8 | var jsfmt = require('../' + libPath + '/index'); 9 | 10 | describe('jsfmt.search', function() { 11 | it('should test basic searching', function() { 12 | var results = jsfmt.search('var param1 = 1, done = function(){}; _.each(param1, done);', '_.each(a, b);'); 13 | results[0].node.loc.should.eql({ 14 | start: { 15 | line: 1, 16 | column: 37 17 | }, 18 | end: { 19 | line: 1, 20 | column: 57 21 | } 22 | }); 23 | results[0].wildcards.a.name.should.eql('param1'); 24 | results[0].wildcards.b.name.should.eql('done'); 25 | }); 26 | 27 | it('should test basic searching with shebang', function() { 28 | var results = jsfmt.search('#!/usr/bin/env node\nvar param1 = 1, done = function(){}; _.each(param1, done);', '_.each(a, b);'); 29 | results[0].wildcards.a.name.should.eql('param1'); 30 | results[0].wildcards.b.name.should.eql('done'); 31 | }); 32 | 33 | it('should be able to match variable declaration', function() { 34 | var results = jsfmt.search('var myA = 1; var myB = 2;', 'var a = b; var c = d;'); 35 | results[0].wildcards.a.name.should.eql('myA'); 36 | results[0].wildcards.b.value.should.eql(1); 37 | results[0].wildcards.c.name.should.eql('myB'); 38 | results[0].wildcards.d.value.should.eql(2); 39 | }); 40 | 41 | it('should be able to perform a basic search inside a block', function() { 42 | var results = jsfmt.search('function test() { return _.map([0, 1, 2], function(val) { return val * val; }); }', 43 | '_.map(a, b)'); 44 | results.length.should.eql(1); 45 | }); 46 | 47 | it('should support wildcard rest params in CallExpression', function() { 48 | // Can transfer arguments 49 | jsfmt.rewrite('jade_mixins["my_key"](argA, argB, argC)', 'jade_mixins[a](...b) -> templates[a](...b)') 50 | .toString().should.eql("templates['my_key'](argA, argB, argC)"); 51 | 52 | // Can drop argument 53 | jsfmt.rewrite('jade_mixins["my_key"](argA, argB, argC)', 'jade_mixins[a](b, c, ...d) -> templates[a](b, c)') 54 | .toString().should.eql("templates['my_key'](argA, argB)"); 55 | }); 56 | 57 | it('should support wildcard rest params in FunctionDeclaration (transfer)', function() { 58 | jsfmt.rewrite('function test(argA, argB, argC) {}', 'function test(...a) {} -> function test(...a) {}') 59 | .toString().should.eql("function test(argA, argB, argC) {\n}"); 60 | }); 61 | 62 | it('should support wildcard rest params in FunctionDeclaration (drop) ', function() { 63 | jsfmt.rewrite('function test(argA, argB, argC) {}', 'function test(a, b, ...c) {} -> function test(a, b) {}') 64 | .toString().should.eql("function test(argA, argB) {\n}"); 65 | 66 | }); 67 | 68 | it('should support wildcard rest params in FunctionExpression', function() { 69 | // Can transfer arguments 70 | jsfmt.rewrite('callMe(function(argA, argB, argC) {})', 'callMe(function(...a) {}) -> callMe(function(...a) {})') 71 | .toString().should.eql("callMe(function (argA, argB, argC) {\n})"); 72 | 73 | // Can drop argument 74 | jsfmt.rewrite('callMe(function(argA, argB, argC) {})', 'callMe(function(a, b, ...c) {}) -> callMe(function(a, b) {})') 75 | .toString().should.eql("callMe(function (argA, argB) {\n})"); 76 | }); 77 | 78 | it('should be able to search for unary expression', function() { 79 | var resultsA = jsfmt.search('!0', '!0'); 80 | resultsA.length.should.eql(1); 81 | 82 | var resultsB = jsfmt.search('var test = !0;', '!0'); 83 | resultsB.length.should.eql(1); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /tests/validate.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | /* global describe,it */ 3 | 'use strict'; 4 | var should = require('should'); 5 | var fs = require('fs'); 6 | 7 | var libPath = process.env.JSFMT_COV ? 'lib-cov' : 'lib'; 8 | var jsfmt = require('../' + libPath + '/index'); 9 | 10 | describe('jsfmt.validate', function() { 11 | it('should test basic validation', function() { 12 | var js = 'return 42;\nvar func = function(test){console.log( test );};'; 13 | var errors = jsfmt.validate(js); 14 | errors.should.have.length(1); 15 | errors[0].index.should.eql(6); 16 | errors[0].lineNumber.should.eql(1); 17 | errors[0].column.should.eql(7); 18 | errors[0].description.should.eql('Illegal return statement'); 19 | }); 20 | 21 | it('should test shebangs', function() { 22 | var js = '#!/usr/bin/env node\nvar func = function(test){console.log( test );};'; 23 | var errors = jsfmt.validate(js); 24 | errors.should.have.length(0); 25 | }); 26 | }); 27 | 28 | describe('jsfmt.validateJSON', function() { 29 | it('should test validation json object', function() { 30 | var js = '{"hello": "world"}'; 31 | var errors = jsfmt.validateJSON(js); 32 | errors.should.have.length(0); 33 | }); 34 | 35 | it('should test validation json array', function() { 36 | var js = '["hello", "world"]'; 37 | var errors = jsfmt.validateJSON(js); 38 | errors.should.have.length(0); 39 | }); 40 | }); 41 | --------------------------------------------------------------------------------