├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── codepaint ├── codepainter.js ├── lib ├── Error.js ├── Inferrer.js ├── MultiInferrer.js ├── Object.js ├── Pipe.js ├── Rule.js ├── Serializer.js ├── Tokenizer.js ├── Transformer.js ├── rules │ ├── end_of_line.js │ ├── indent_style_and_size.js │ ├── index.js │ ├── insert_final_newline.js │ ├── quote_type.js │ ├── space_after_anonymous_functions.js │ ├── space_after_control_statements.js │ ├── spaces_around_operators.js │ ├── spaces_in_brackets.js │ └── trim_trailing_whitespace.js ├── styles │ ├── codepainter.json │ ├── hautelook.json │ ├── idiomatic.json │ └── mediawiki.json └── util │ ├── index.js │ └── string.js ├── package.json └── test ├── cases.js ├── cases ├── .editorconfig ├── end_of_line │ ├── .gitattributes │ ├── crlf │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── lf │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── indent_style_and_size │ ├── space_2 │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ ├── space_4 │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── tab │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── insert_final_newline │ ├── false │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── true │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── quote_type │ ├── auto │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ ├── double │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── single │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── space_after_anonymous_functions │ ├── false │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── true │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── space_after_control_statements │ ├── false │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── true │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── spaces_around_operators │ ├── false │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ ├── hybrid │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── true │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json ├── spaces_in_brackets │ ├── false │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ ├── hybrid │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json │ └── true │ │ ├── expected.js │ │ ├── input.js │ │ ├── sample.js │ │ └── style.json └── trim_trailing_whitespace │ ├── false │ ├── expected.js │ ├── input.js │ ├── sample.js │ └── style.json │ └── true │ ├── expected.js │ ├── input.js │ ├── sample.js │ └── style.json ├── codepaint.js └── inputs ├── input.js └── sample.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.js] 4 | ; EditorConfig supported properties 5 | indent_style = tab 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | ; CodePainter extended properties 10 | quote_type = auto 11 | spaces_around_operators = true 12 | space_after_control_statements = true 13 | space_after_anonymous_functions = false 14 | spaces_in_brackets = false 15 | 16 | [*.json] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.yml] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [node_modules/**.js] 25 | codepaint = false 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | *.log 4 | *.suo 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .editorconfig 3 | .gitattributes 4 | .npmignore 5 | .travis.yml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Jakub Wieczorek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Painter 2 | 3 | [![Build Status][]](http://travis-ci.org/jedmao/codepainter) 4 | [![Dependency Status][]](https://gemnasium.com/jedmao/codepainter) 5 | [![NPM version](https://badge.fury.io/js/codepainter.svg)](http://badge.fury.io/js/codepainter) 6 | [![Views](https://sourcegraph.com/api/repos/github.com/jedmao/codepainter/counters/views-24h.png)](https://sourcegraph.com/github.com/jedmao/codepainter) 7 | 8 | [![NPM](https://nodei.co/npm/codepainter.png?downloads=true)](https://nodei.co/npm/codepainter/) 9 | 10 | Code Painter is a JavaScript beautifier that can transform JavaScript files 11 | into the formatting style of your choice. Style settings can be supplied via 12 | predefined styles, a custom JSON file, command line settings, [EditorConfig][] 13 | settings or it can even be inferred from one or more sample files. For example, 14 | you could provide a code snippet from the same project with which the new code 15 | is intended to integrate. 16 | 17 | It uses the excellent [Esprima parser][] by [Ariya Hidayat][] and his 18 | [contributors][] — thanks! 19 | 20 | The name is inspired by Word's Format Painter, which does a similar job for 21 | rich text. 22 | 23 | 24 | ## Requirements 25 | 26 | Code Painter requires [Node.js][] version 0.10.6 or above. 27 | 28 | 29 | ## Installation 30 | 31 | $ npm install codepainter 32 | 33 | To access the command globally, do a global install: 34 | 35 | $ npm install -g codepainter 36 | 37 | *nix users might also have to add the following to their .bashrc file: 38 | 39 | PATH=$PATH:/usr/local/share/npm/bin 40 | 41 | 42 | ## CLI Usage 43 | 44 | You can see the usage in the CLI directly by typing `codepaint` or 45 | `codepaint --help`. 46 | 47 | ``` 48 | $ codepaint --help 49 | 50 | Code Painter beautifies JavaScript. 51 | 52 | Usage: codepaint [options] 53 | 54 | Commands: 55 | 56 | infer [options] ... Infer formatting style from file(s) 57 | xform [options] ... Transform file(s) to specified style 58 | 59 | Options: 60 | 61 | -h, --help output help information 62 | -V, --version output version information 63 | 64 | 65 | $ codepaint infer --help 66 | 67 | Infer formatting style from file(s) 68 | 69 | Usage: infer [options] ... 70 | 71 | Options: 72 | 73 | -h, --help output help information 74 | -d, --details give a detailed report with trend scores 75 | --ini use ini file format 76 | 77 | Examples: 78 | 79 | $ codepaint infer "**/*.js" 80 | $ codepaint infer "**/*view.js" "**/*model.js" 81 | $ codepaint infer -d "**/*.js" 82 | $ codepaint infer --ini "**/*.js" 83 | 84 | 85 | $ codepaint xform --help 86 | 87 | Transform file(s) to specified formatting style 88 | 89 | Usage: xform [options] ... 90 | 91 | Options: 92 | 93 | -h, --help output help information 94 | -i, --infer code sample(s) to infer 95 | -p, --predef cascade predefined style (e.g., idiomatic) 96 | -j, --json cascade JSON style over predef style 97 | -s, --style = cascade explicit style over JSON 98 | -e, --editor-config cascade EditorConfig style over all others 99 | 100 | Examples: 101 | 102 | $ codepaint xform "**/*.js" 103 | $ codepaint xform "**/*view.js" "**/*model.js" 104 | $ codepaint xform %s "**/*.js" -i sample.js 105 | $ codepaint xform %s "**/*.js" -p idiomatic 106 | $ codepaint xform %s "**/*.js" -j custom.json 107 | $ codepaint xform %s "**/*.js" -s quote_type=null 108 | $ codepaint xform %s "**/*.js" -s indent_style=space -s indent_size=4 109 | $ codepaint xform %s "**/*.js" -e 110 | ``` 111 | 112 | 113 | ## Library Usage 114 | 115 | ```js 116 | var codepaint = require('codepainter'); 117 | ``` 118 | 119 | Library usage is intended to be every bit the same as CLI usage, so you can 120 | expect the same options and arguments that the CLI requires. 121 | 122 | ### .infer(< path|glob|globs|ReadableStream >[,options][,callback]) 123 | 124 | Example usage: 125 | 126 | ```js 127 | codepaint.infer('**/**.js', {details: true}, function(inferredStyle) { 128 | console.log(inferredStyle); 129 | }); 130 | ``` 131 | 132 | ### .xform(< path|glob|globs|ReadableStream >[,options][,callback]) 133 | 134 | Example usage: 135 | 136 | ```js 137 | codepaint.xform('input.js', {indent_size: 4}, function(err, xformed, skipped, errored){ 138 | if (err) { 139 | throw err; 140 | } 141 | console.log('transformed:', xformed); 142 | console.log('skipped:', skipped); 143 | console.log('errored:', errored); 144 | }); 145 | ``` 146 | 147 | The following example infers formatting style from `sample.js` and uses that 148 | inferred style to transform all .js files under the current directory. 149 | 150 | ```js 151 | codepaint.infer('sample.js', function(inferredStyle) { 152 | codepainter.xform('**/**.js', {style: inferredStyle}); 153 | }); 154 | ``` 155 | 156 | 'sample.js' could also be an array or any readable stream. `transform` is an 157 | alias for the `xform` method. You can use either one. 158 | 159 | Great, so that's all nice and simple, but maybe you want to do something with 160 | the output. We start by creating an instance of the Transformer class. 161 | 162 | ```js 163 | var Transformer = require('codepainter').Transformer; 164 | var transformer = new Transformer(); 165 | ``` 166 | 167 | Now, we can listen to any of the following events: 168 | 169 | ### cascade 170 | 171 | Every time one style cascades over another. 172 | 173 | ```js 174 | transformer.on('cascade', cascade); 175 | function cascade(styleBefore, styleToMerge, styleType) { 176 | // code here 177 | } 178 | ``` 179 | 180 | ### transform 181 | 182 | Every time a file is transformed. 183 | 184 | ```js 185 | transformer.on('transform', function(transformed, path) { 186 | // code here 187 | } 188 | ``` 189 | 190 | ### error 191 | 192 | ```js 193 | transformer.on('error', function(err, inputPath) { 194 | // code here 195 | } 196 | ``` 197 | 198 | ### end 199 | 200 | When all transformations have taken place. 201 | 202 | ```js 203 | transformer.on('end', function(err, transformed, skipped, errored) { 204 | // code here 205 | } 206 | ``` 207 | 208 | Of course, none of these events will fire if you don't perform the transform: 209 | 210 | `transformer.transform(globs, options);` 211 | 212 | 213 | ## CLI Examples 214 | 215 | $ codepaint infer "**/*.js" 216 | 217 | Infers formatting style from all .js files under the current directory into a 218 | single JSON object, which you can pipe out to another file if you want. It can 219 | then be used in a transformation (below). 220 | 221 | If you want to create an `.editorconfig` file, use the `--ini` flag: 222 | 223 | $ codepaint infer --ini "**/*.js" > .editorconfig 224 | 225 | Moving on to the `xform` sub-command: 226 | 227 | $ codepaint xform "**/*.js" 228 | 229 | This doesn't transform any files, but it does show you how many files would be 230 | affected by the glob you've provided. Globs absolutely *must* be in quotes or 231 | you will experience unexpected behavior! 232 | 233 | $ codepaint xform -i infer.js "**/*.js" 234 | 235 | Transforms all .js files under the current directory with the formatting style 236 | inferred from infer.js 237 | 238 | $ codepaint xform -p idiomatic "**/*.js" 239 | 240 | Transforms all .js files under the current directory with a Code Painter 241 | pre-defined style. In this case, Idiomatic. The only other pre-defined styles 242 | available at this time are mediawiki and hautelook. 243 | 244 | $ codepaint xform -j custom.json "**/*.js" 245 | 246 | Transforms all .js files under the current directory with a custom style in 247 | JSON format. 248 | 249 | $ codepaint xform -s indent_style=space -s indent_size=4 "**/*.js" 250 | 251 | Transforms all .js files under the current directory with 2 settings: 252 | `indent_style=space` and `indent_size=4`. You can specify as many settings as 253 | you want and you can set values to `null` to disable them. 254 | 255 | $ codepaint xform -e "**/*.js" 256 | 257 | Transforms all .js files under the current directory with the EditorConfig 258 | settings defined for each individual file. 259 | 260 | Refer to [EditorConfig Core Installation][] for installation instructions and 261 | [EditorConfig][] for more information, including how to define and use 262 | `.editorconfig` files. 263 | 264 | $ codepaint xform -i infer.js -p idiomatic -j custom.json 265 | -s end_of_line=null -e "**/*.js" 266 | 267 | As you can see, you can use as many options as you want. Code Painter will 268 | cascade your styles and report how the cascade has been performed, like so: 269 | 270 | ``` 271 | Inferred style: 272 | + indent_style = tab 273 | + insert_final_newline = true 274 | + quote_type = auto 275 | + space_after_anonymous_functions = false 276 | + space_after_control_statements = false 277 | + spaces_around_operators = false 278 | + trim_trailing_whitespace = false 279 | + spaces_in_brackets = false 280 | 281 | hautelook style: 282 | * indent_style = space 283 | + indent_size = 4 284 | * trim_trailing_whitespace = true 285 | + end_of_line = lf 286 | = insert_final_newline = true 287 | = quote_type = auto 288 | * spaces_around_operators = true 289 | = space_after_control_statements = true 290 | = space_after_anonymous_functions = false 291 | * spaces_in_brackets = false 292 | 293 | Supplied JSON file: 294 | * space_after_control_statements = true 295 | = indent_style = space 296 | * indent_size = 3 297 | 298 | Inline styles: 299 | x end_of_line = null 300 | 301 | Editor Config: 302 | + applied on a file-by-file basis 303 | 304 | ........................... 305 | 306 | REPORT: 27 files transformed 307 | ``` 308 | 309 | 310 | ## Supported Style Properties 311 | 312 | ### **codepaint**: *false* 313 | 314 | Tells CodePainter to skip the file (no formatting). This property really 315 | only makes sense if you are using the `--editor-config` CLI option. This 316 | allows you to, for example, skip a vendor scripts directory. 317 | 318 | ### EditorConfig properties 319 | **indent_style**, **indent_size**, **end_of_line**, 320 | **trim_trailing_whitespace** and **insert_final_newline**. 321 | 322 | Refer to [EditorConfig's documentation][] for more information. 323 | 324 | ### **quote_type**: *single*, *double*, *auto* 325 | 326 | Specifies what kind of quoting you would like to use for string literals: 327 | 328 | ```js 329 | console.log("Hello world!"); // becomes console.log('Hello world!'); 330 | ``` 331 | 332 | Adds proper escaping when necessary, obviously. 333 | 334 | ```js 335 | console.log('Foo "Bar" Baz'); // becomes console.log("Foo \"Bar\" Baz"); 336 | ``` 337 | 338 | The *auto* setting infers the quoting with a precedence toward *single* 339 | mode. 340 | 341 | ```js 342 | console.log("Foo \"Bar\" Baz"); // becomes console.log('Foo "Bar" Baz'); 343 | console.log('Foo \'Bar\' Baz'); // becomes console.log("Foo 'Bar' Baz"); 344 | ``` 345 | 346 | ### **space_after_control_statements**: *true*, *false* 347 | 348 | Specifies whether or not there should be a space between if/for/while and 349 | the following open paren: 350 | 351 | If true: 352 | 353 | ```js 354 | if(x === 4) {} // becomes if (x === 4) {} 355 | ``` 356 | 357 | If false: 358 | 359 | ```js 360 | while (foo()) {} // becomes while(foo()) {} 361 | ``` 362 | 363 | ### **space_after_anonymous_functions**: *true*, *false* 364 | 365 | Specifies whether or not there should be a space between the `function` 366 | keyword and the following parens in anonymous functions: 367 | 368 | ```js 369 | function(x) {} // becomes function (x) {} 370 | ``` 371 | 372 | ### **spaces_around_operators**: *true*, *false*, *hybrid* 373 | 374 | Specifies whether or not there should be spaces around operators such as 375 | `+,=,+=,>=,!==`. 376 | 377 | ```js 378 | x = 4; // becomes x=4; 379 | a>=b; // becomes a >= b; 380 | a>>2; // becomes a >> 2; 381 | ``` 382 | 383 | Unary operators `!,~,+,-` are an exception to the rule; thus, no spaces 384 | are added. Also, any non-conditional `:` operators do not receive a space 385 | (i.e., the switch...case operator and property identifiers): 386 | 387 | ```js 388 | switch (someVar) { 389 | case 'foo' : // becomes case 'foo': 390 | var x = {foo : 'bar'}; // becomes {foo: 'bar'} 391 | break; 392 | } 393 | ``` 394 | 395 | *Hybrid* mode is mostly like the *true* setting, except it behaves as 396 | *false* on operators `*,/,%`: 397 | 398 | ```js 399 | var x = 4 * 2 + 1 / 7; // becomes var x = 4*2 + 1/7; 400 | ``` 401 | 402 | ### **spaces_in_brackets**: *true*, *false*, *hybrid* 403 | 404 | Specifies whether or not there should be spaces inside brackets, which 405 | includes `(),[],{}`. Empty pairs of brackets will always be shortened. 406 | 407 | If true: 408 | 409 | ```js 410 | if (x === 4) {} // becomes if ( x === 4 ) {} 411 | ``` 412 | 413 | If false: 414 | 415 | ```js 416 | if ( x === 4 ) {} // becomes if (x === 4) 417 | ``` 418 | 419 | The *hybrid* setting mostly reflects Idiomatic style. Refer to 420 | [Idiomatic Style Manifesto][]. 421 | 422 | 423 | ## Pipes and Redirects 424 | 425 | On a unix command-line, you can transform a file from the stdin stream: 426 | 427 | $ codepaint xform -s indent_size=2 < input.js 428 | 429 | The stdout stream works a bit differently. Since Code Painter can transform 430 | multiple files via glob syntax, it wouldn't make sense to output the 431 | transformations of all those files to a single stream. Instead, only if you 432 | are using stdin as input and no `-o, --output` option is provided will Code 433 | Painter send the transformation to the stdout stream: 434 | 435 | $ codepaint xform -s indent_size=2 < input.js > output.js 436 | 437 | Piping is supported as well: 438 | 439 | $ codepaint xform -s indent_size=2 < input.js | othercommand` 440 | 441 | 442 | ## Git Clean and Smudge Filters 443 | 444 | Because Code Painter supports stdin and stdout streams, as explained above, 445 | Git "clean" and "smudge" filters can be used as well. 446 | 447 | **CAUTION:** My personal experience has shown inconsistent results, so use with 448 | caution! Also, please contact me if you figure out how to do this without any 449 | hiccups. 450 | 451 | First, change your `.gitattributes` file to use your new filter. We'll call it 452 | "codepaint". 453 | 454 | *.js filter=codepaint 455 | 456 | Then, tell Git what the "codepaint" filter does. First, we will convert code 457 | to tabs upon checkout with the "smudge" filter: 458 | 459 | $ git config filter.codepaint.smudge "codepaint xform -s indent_style=tab" 460 | 461 | Then, upon staging of files with the Git "clean" filter, the style is restored 462 | to spaces and cleaned to reflect any other style preferences you may have set: 463 | 464 | $ git config filter.codepaint.clean "codepaint xform -p style.json" 465 | 466 | This allows you to work in the indentation of your preference without stepping 467 | on anyone's toes and checking in inconsistent indentation. Or maybe you have 468 | your own preference for spaces around operators? Smudge it to your preference 469 | and clean it to your company's formatting style. 470 | 471 | **WARNING:** Git "clean" and "smudge" filters are bypassed with GitHub for 472 | Windows. 473 | 474 | Refer to [Git's documentation][] for more information on Git "smudge" and 475 | "clean" filters. 476 | 477 | 478 | ## Recommended Use 479 | 480 | It is highly recommended that you use the EditorConfig approach to painting 481 | your code. To do so, do the following: 482 | 483 | Place an `.editorconfig` file at your project root. Refer to this 484 | project's [.editorconfig][] file for a point of reference as to how this 485 | might look. You can also scatter `.editorconfig` files elsewhere 486 | throughout your project to prevent Code Painter from doing any 487 | transformations (e.g., your vendor scripts folders). In this case, the 488 | `.editorconfig` file would simply read: `codepaint = false`. 489 | 490 | Specify Code Painter in your devDependencies section of your package.json: 491 | 492 | ```json 493 | { 494 | "devDependencies": { 495 | "codepainter": "~0.3.15" 496 | } 497 | } 498 | ``` 499 | 500 | Define a `codepaint` script in the scripts section of your package.json: 501 | 502 | ```json 503 | { 504 | "scripts": { 505 | "codepaint": "node node_modules/codepainter/bin/codepaint xform -e **/**.js" 506 | } 507 | } 508 | ``` 509 | 510 | If you have Code Painter installed globally, the command is as simple as: 511 | 512 | ```json 513 | { 514 | "scripts": { 515 | "codepaint": "codepaint xform -e **/**.js" 516 | } 517 | } 518 | ``` 519 | 520 | But Code Painter wouldn't install globally by default, so the first 521 | approach is the recommended one. Then, you can run Code Painter on your 522 | entire project, consistently, with the following command: 523 | 524 | $ npm run-script codepaint 525 | 526 | You *could* run `codepaint` manually every time you want to do it, but you 527 | might find this next `.bashrc` shortcut more useful. The idea is to run 528 | this `gc` alias to a newly-defined `codepaint_git_commit` function. This, 529 | you do instead of running `git commit`. The caveat is that you need to 530 | stage your changes with `git add` before doing so. This is because the 531 | command runs `codepaint` only on staged `.js` files. Aside from this 532 | caveat, you can commit things mostly the same as you were used to before. 533 | Now, `gc` can paint your code before a commit and bail-out of the commit 534 | if there are issues with the process (e.g., JavaScript parse errors). The 535 | idea of formatting code before a commit is definitely controversial, but 536 | if you choose to do so anyway, here's the neat trick to put in your 537 | `.bashrc` file: 538 | 539 | ```bash 540 | # Example usage: gc "initial commit" 541 | alias gc=codepaint_git_commit 542 | codepaint_git_commit() { 543 | # 1. Gets a list of .js files in git staging and sends the list to CodePainter. 544 | # 2. CodePainter with the -e flag applies rules defined in your EditorConfig file(s). 545 | # 3. After CodePainter is done, your args are passed to the `git commit` command. 546 | jsfiles=$(git diff --name-only --cached | egrep '\.js$') 547 | if [ $jsfiles ]; then 548 | ./node_modules/codepainter/bin/codepaint xform -e $jsfiles 549 | fi 550 | git commit -m "$@" 551 | } 552 | ``` 553 | 554 | You could also compare Code Painter's output with the original file on a Git 555 | pre-commit hook and reject the commit if the files don't match. Let's be real 556 | though. This would happen almost *every* time you commit and it would be a 557 | royal pain in your workflow. 558 | 559 | There are so many ways you could use Code Painter. How do you prefer to use 560 | Code Painter? Feel free to contact me, Jed, with tips or suggestions. See 561 | [package.json][] for contact information). 562 | 563 | 564 | ## Enforcing 565 | 566 | Code Painter can be used to enforce a formatting style in a number of creative 567 | ways. To fail [Travis CI][] if code does not comply with your organization's 568 | style guide, the process would work something like this: 569 | 570 | 1. Run Code Painter on the code base. 571 | 1. Fail Travis if any file changes are detected. This encourages developers 572 | to run Code Painter before pushing code. 573 | 574 | Running Code Painter with Travis is as simple as adding the command to the 575 | `before_script` section of your `.travis.yml` file: 576 | ```yaml 577 | before_script: 578 | - node node_modules/codepainter/bin/codepaint xform -e "**/**.js" 579 | ``` 580 | 581 | Notice I didn't use the command `npm run-script codepaint`. This is because 582 | there were issues with the double-quoted glob being processed. If you find a 583 | way around this, please let me know. 584 | 585 | Next, you need to create a node script that exits the node process with a 586 | non-zero code if any changes are detected. This, we do with `git diff`: 587 | ```js 588 | var clc = require('cli-color'); 589 | var spawn = require('child_process').spawn; 590 | var git = spawn('git', ['diff', '--name-only']); 591 | 592 | git.stdout.setEncoding('utf8'); 593 | git.stdout.on('data', exitWithErrorIfFilesHaveChanged); 594 | 595 | function exitWithErrorIfFilesHaveChanged(data) { 596 | console.log(); 597 | console.log(clc.red('Error: The following files do not conform to the CompanyX style guide:')); 598 | console.log(data); 599 | process.exit(1); 600 | } 601 | ``` 602 | 603 | Finally, you can add this script to your `.travis.yml` file in the `script` 604 | section: 605 | ```yaml 606 | script: 607 | - node gitdiff.js 608 | ``` 609 | 610 | Violä! Travis should now fail if code does not comply with your organization's 611 | style guide. 612 | 613 | 614 | ## License 615 | 616 | Released under the MIT license. 617 | 618 | [Build Status]: https://secure.travis-ci.org/jedmao/codepainter.svg?branch=master 619 | [Dependency Status]: https://gemnasium.com/jedmao/codepainter.svg 620 | [Esprima parser]: http://esprima.org/ 621 | [Ariya Hidayat]: http://ariya.ofilabs.com/ 622 | [contributors]: https://github.com/ariya/esprima/graphs/contributors 623 | [Node.js]: http://nodejs.org/ 624 | [EditorConfig]: http://editorconfig.org/ 625 | [EditorConfig's documentation]: http://editorconfig.org/ 626 | [EditorConfig Core Installation]: /editorconfig/editorconfig-core#installation 627 | [Idiomatic Style Manifesto]: /rwldrn/idiomatic.js/#whitespace 628 | [.editorconfig]: .editorconfig 629 | [package.json]: package.json 630 | [Git's documentation]: http://git-scm.com/book/ch7-2.html 631 | [Travis CI]: https://travis-ci.org/ 632 | 633 | 634 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/jedmao/codepainter/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 635 | 636 | -------------------------------------------------------------------------------- /bin/codepaint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var clc = require('cli-color'); 4 | var crypto = require('crypto'); 5 | var editorconfig = require('editorconfig'); 6 | var extend = require('node.extend'); 7 | var fs = require('fs'); 8 | var glob = require('glob'); 9 | var path = require('path'); 10 | var program = require('gitlike-cli'); 11 | 12 | var codepaint = require('../'); 13 | var package = require('../package'); 14 | var Transformer = require('../lib/Transformer'); 15 | 16 | 17 | var style = {}; 18 | var useColor = true; 19 | 20 | function setting(val) { 21 | return keyValue(val, style); 22 | } 23 | 24 | function keyValue(val, store) { 25 | val = val.split('='); 26 | store[val[0]] = parseValue(val[1]); 27 | return store; 28 | } 29 | 30 | function parseValue(value){ 31 | try { 32 | return JSON.parse(value); 33 | } catch(e){ 34 | return value; 35 | } 36 | } 37 | 38 | program.on('error', function(err){ 39 | console.log(''); 40 | console.log(clc.red(' ' + err.name + ':', err.message)); 41 | err.command.outputUsage(); 42 | err.command.outputCommands(); 43 | err.command.outputOptions(); 44 | console.log(); 45 | process.exit(1); 46 | }); 47 | 48 | program 49 | 50 | .version(package.version) 51 | .description('Code Painter beautifies JavaScript.') 52 | 53 | .command('infer ...') 54 | .description('Infer formatting style from file(s)') 55 | .action(infer) 56 | .option('-d, --details', 'give a detailed report with trend scores') 57 | .option('--ini', 'use ini file format') 58 | .on('help', function(cmd){ 59 | cmd.outputIndented('Examples', [ 60 | '$ ' + clc.cyan('codepaint infer "**/*.js"'), 61 | '$ codepaint infer ' + clc.cyan('"**/*view.js" "**/*model.js"'), 62 | '$ codepaint infer ' + clc.cyan('-d') + ' "**/*.js"', 63 | '$ codepaint infer ' + clc.cyan('--ini') + ' "**/*.js"', 64 | ]); 65 | }).parent 66 | 67 | .command('xform ...') 68 | .description('Transform file(s) to specified formatting style') 69 | .action(xform) 70 | .option('-i, --infer ', 'code sample(s) to infer') 71 | .option('-p, --predef ', 'cascade predefined style (e.g., idiomatic)') 72 | .option('-j, --json ', 'cascade JSON style over predef style') 73 | .option('-s, --style =', 'cascade explicit style over JSON', setting) 74 | .option('-e, --editor-config', 'cascade EditorConfig style over all others') 75 | .option('-o, --output ', 'output a single transformation to one file') 76 | .on('help', function(cmd){ 77 | cmd.outputIndented('Examples', [ 78 | '$ ' + clc.cyan('codepaint xform "**/*.js"'), 79 | '$ codepaint xform ' + clc.cyan('"**/*view.js" "**/*model.js"'), 80 | '$ codepaint xform %s "**/*.js" ' + clc.cyan('-i sample.js'), 81 | '$ codepaint xform %s "**/*.js" ' + clc.cyan('-p idiomatic'), 82 | '$ codepaint xform %s "**/*.js" ' + clc.cyan('-j custom.json'), 83 | '$ codepaint xform %s "**/*.js" ' + clc.cyan('-s quote_type=null'), 84 | '$ codepaint xform %s "**/*.js" ' + clc.cyan('-s indent_style=space -s indent_size=4'), 85 | '$ codepaint xform %s "**/*.js" ' + clc.cyan('-e') 86 | ]); 87 | }).parent 88 | 89 | .parse(process.argv); 90 | 91 | 92 | function infer(args, options) { 93 | codepaint.infer(args.globs, options, function(style) { 94 | if (options.ini) { 95 | console.log('[*.js]'); 96 | Object.keys(style).forEach(function(key) { 97 | console.log(key, '=', style[key] + ';'); 98 | }); 99 | } else { 100 | console.log(JSON.stringify(style)); 101 | } 102 | }); 103 | } 104 | 105 | function xform(args, options) { 106 | 107 | style = {}; 108 | 109 | var transformer = new Transformer(); 110 | var errors = []; 111 | var showReport = args.globs[0] !== process.stdin; 112 | args.globs = args.globs.filter(function (ele) { return typeof ele === 'string' }); 113 | 114 | if (showReport) { 115 | 116 | transformer.on('cascade', cascade); 117 | 118 | transformer.on('transform', function(transformed, path){ 119 | writeDot('.'); 120 | }); 121 | 122 | var errorDot = useColor ? clc.red('.') : '.'; 123 | transformer.on('error', function(err, inputPath){ 124 | err.inputPath = inputPath; 125 | errors.push(err); 126 | writeDot(errorDot); 127 | }); 128 | } 129 | 130 | transformer.on('end', function(err, transformed, skipped, errored){ 131 | if (errors.length && showReport) { 132 | console.log(); 133 | } 134 | errors.forEach(function(err2){ 135 | error(err2.message + ': ' + err2.inputPath); 136 | }); 137 | if (showReport) { 138 | displayFinalMessage(transformed, skipped, errored); 139 | } 140 | if (err) process.exit(1); 141 | }); 142 | 143 | transformer.transform(args.globs, options); 144 | } 145 | 146 | function cascade(styleBefore, styleToMerge, styleType){ 147 | if (Object.keys(styleToMerge).length === 0){ 148 | return; 149 | } 150 | console.log(); 151 | console.log(' ' + styleType + ':'); 152 | var color; 153 | Object.keys(styleToMerge).forEach(function(key){ 154 | var msg = key + ' = ' + styleToMerge[key]; 155 | if (key in style) { 156 | if (style[key] === styleToMerge[key]){ 157 | msg = '= ' + msg; 158 | color = clc.blackBright; 159 | } else if (styleToMerge[key] === null){ 160 | msg = ' x ' + msg; 161 | delete style[key]; 162 | console.log(useColor ? clc.red(msg) : msg); 163 | return; 164 | } else { 165 | msg = '* ' + msg; 166 | color = clc.cyan; 167 | } 168 | } else { 169 | msg = '+ ' + msg; 170 | color = clc.green; 171 | } 172 | console.log(' ' + (useColor ? color(msg) : msg)); 173 | }); 174 | process.stdout.write('\n '); 175 | } 176 | 177 | var dotCount = 0; 178 | function writeDot(dot) { 179 | if ( ++dotCount%70 === 0 ) { 180 | process.stdout.write('\n '); 181 | } 182 | process.stdout.write(dot); 183 | } 184 | 185 | function error(msg){ 186 | msg = 'Error: ' + msg; 187 | if (useColor) { 188 | msg = clc.red(msg); 189 | } 190 | msg = '\n ' + msg; 191 | if (typeof arguments[1] !== 'undefined') { 192 | console.log(msg, arguments[1]); 193 | } else { 194 | console.log(msg); 195 | } 196 | } 197 | 198 | function displayFinalMessage(transformed, skipped, errored) { 199 | console.log(); 200 | console.log(); 201 | var msg = ' REPORT: %s transformed. %s errored. %s skipped (no rules).'; 202 | if (useColor) { 203 | msg = clc.magenta(msg); 204 | } 205 | console.log(msg, transformed, errored, skipped); 206 | console.log(); 207 | } 208 | -------------------------------------------------------------------------------- /codepainter.js: -------------------------------------------------------------------------------- 1 | var CodePainterObject = require('./lib/Object'); 2 | var MultiInferrer = require('./lib/MultiInferrer'); 3 | var Transformer = require('./lib/Transformer'); 4 | var util = require('./lib/util'); 5 | 6 | 7 | function CodePainter() { 8 | CodePainter.super_.apply(this, arguments); 9 | } 10 | 11 | util.inherits(CodePainter, CodePainterObject, { 12 | 13 | infer: function() { 14 | var multiInferrer = new MultiInferrer(); 15 | multiInferrer.infer.apply(multiInferrer, arguments); 16 | }, 17 | 18 | xform: function() { 19 | var transformer = new Transformer(); 20 | transformer.transform.apply(transformer, arguments); 21 | }, 22 | 23 | transform: function() { 24 | this.xform.apply(this, arguments); 25 | }, 26 | 27 | Inferrer: MultiInferrer, 28 | 29 | Transformer: Transformer 30 | }); 31 | 32 | module.exports = new CodePainter(); 33 | -------------------------------------------------------------------------------- /lib/Error.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'); 2 | 3 | function CodePainterError(message) { 4 | CodePainterError.super_.call(this); 5 | this.message = message || ''; 6 | } 7 | 8 | module.exports = util.inherits(CodePainterError, Error, { 9 | name: 'CodePainterError' 10 | }); 11 | -------------------------------------------------------------------------------- /lib/Inferrer.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var extend = require('node.extend'); 3 | var stream = require('stream'); 4 | 5 | var CodePainterError = require('./Error'); 6 | var CodePainterObject = require('./Object'); 7 | var Pipe = require('./Pipe'); 8 | var rules = require('./rules'); 9 | var Tokenizer = require('./Tokenizer'); 10 | var util = require('./util'); 11 | 12 | 13 | function Inferrer() { 14 | Inferrer.super_.apply(this, arguments); 15 | } 16 | 17 | module.exports = util.inherits(Inferrer, CodePainterObject, { 18 | 19 | name: 'Inferrer', 20 | 21 | infer: function(input, callback, Rule) { 22 | this.openInput(input); 23 | this.initTokenizer(); 24 | this.inferRules(Rule); 25 | this.initTokenizerEnd(callback); 26 | }, 27 | 28 | openInput: function(input) { 29 | var inputStream = (input instanceof stream.Readable) ? 30 | input : 31 | fs.createReadStream(input); 32 | inputStream.setEncoding('utf8'); 33 | this.inputStream = inputStream; 34 | }, 35 | 36 | initTokenizer: function() { 37 | var tokenizer = new Tokenizer(); 38 | tokenizer.on('error', function(err) { 39 | this.error(err.message); 40 | }.bind(this)); 41 | this.tokenizer = tokenizer; 42 | this.inputStream.pipe(this.tokenizer); 43 | }, 44 | 45 | inferRules: function(Rule) { 46 | if (typeof Rule !== 'undefined') { 47 | this.inferRule(Rule); 48 | } else { 49 | rules.forEach(this.inferRule.bind(this)); 50 | } 51 | }, 52 | 53 | inferRule: function(Rule) { 54 | new Rule().infer(this.tokenizer, onInferEnd.bind(this)); 55 | function onInferEnd(inferredStyle) { 56 | this.style = extend(this.style, inferredStyle); 57 | } 58 | }, 59 | 60 | initTokenizerEnd: function(callback) { 61 | this.tokenizer.on('end', function() { 62 | callback(this.style); 63 | }.bind(this)); 64 | } 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /lib/MultiInferrer.js: -------------------------------------------------------------------------------- 1 | var editorconfig = require('editorconfig'); 2 | var fs = require('fs'); 3 | var stream = require('stream'); 4 | 5 | var CodePainterError = require('./Error'); 6 | var CodePainterObject = require('./Object'); 7 | var Inferrer = require('./Inferrer'); 8 | var util = require('./util'); 9 | 10 | 11 | function MultiInferrer() { 12 | MultiInferrer.super_.apply(this, arguments); 13 | } 14 | 15 | module.exports = util.inherits(MultiInferrer, CodePainterObject, { 16 | 17 | name: 'MultiInferrer', 18 | 19 | infer: function(globs, options, callback, Rule) { 20 | 21 | if (typeof options === 'function') { 22 | Rule = callback; 23 | callback = options; 24 | options = undefined; 25 | } 26 | 27 | this.options = options || {}; 28 | this.callback = callback; 29 | this.Rule = Rule; 30 | this.style = {}; 31 | 32 | if (typeof globs === 'string' || globs instanceof stream.Readable) { 33 | this.onGlobPath(globs); 34 | } else if (globs && globs[0] instanceof stream.Readable) { 35 | this.onGlobPath(globs[0]); 36 | } else { 37 | util.reduceGlobs(globs, this.onGlobPaths.bind(this)); 38 | } 39 | }, 40 | 41 | onGlobPaths: function(paths) { 42 | this.pathsFound = paths.length; 43 | paths.forEach(this.onGlobPath.bind(this)); 44 | }, 45 | 46 | onGlobPath: function (path) { 47 | var inferrer = new Inferrer(); 48 | if (typeof path === 'string') { 49 | if (!fs.statSync(path).isFile()) { 50 | return; 51 | } 52 | var editorConfigStyle = editorconfig.parse(path); 53 | if (editorConfigStyle.codepaint === false) { 54 | this.finalizeScores(); 55 | return; 56 | } 57 | } 58 | inferrer.infer(path, this.updateScore.bind(this), this.Rule); 59 | }, 60 | 61 | updateScore: function(style) { 62 | Object.keys(style).forEach(function(key) { 63 | var rule = this.style[key]; 64 | if (!rule) { 65 | rule = this.style[key] = {}; 66 | } 67 | var setting = style[key]; 68 | rule[setting] = (rule[setting] || 0) + 1; 69 | }.bind(this)); 70 | this.finalizeScores(); 71 | }, 72 | 73 | finalizeScores: function() { 74 | if (--this.pathsFound) { 75 | return; 76 | } 77 | if (!this.options.details) { 78 | Object.keys(this.style).forEach(this.identifyTrend.bind(this)); 79 | this.parseValues(); 80 | } 81 | this.callback(this.style); 82 | }, 83 | 84 | identifyTrend: function(key) { 85 | var rule = this.style[key]; 86 | var trend, max = -1; 87 | Object.keys(rule).forEach(function(setting) { 88 | var score = parseInt(rule[setting], 10); 89 | if (score > max) { 90 | max = score; 91 | trend = setting; 92 | } 93 | }); 94 | this.style[key] = trend; 95 | }, 96 | 97 | parseValues: function() { 98 | Object.keys(this.style).forEach(function(key) { 99 | try { 100 | this.style[key] = JSON.parse(this.style[key]); 101 | } catch(e) { 102 | } 103 | }.bind(this)); 104 | } 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /lib/Object.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | 3 | var CodePainterError = require('./Error'); 4 | var util = require('./util'); 5 | 6 | 7 | function CodePainterObject(onError) { 8 | CodePainterObject.super_.call(this); 9 | this.onError = onError || this.onError; 10 | 11 | CodePainterObjectError.prototype.name = this.name + 'Error'; 12 | function CodePainterObjectError(message) { 13 | CodePainterObjectError.super_.call(this, message); 14 | } 15 | 16 | util.inherits(CodePainterObjectError, CodePainterError); 17 | this.Error = CodePainterObjectError; 18 | } 19 | 20 | module.exports = util.inherits(CodePainterObject, EventEmitter, { 21 | 22 | error: function(message) { 23 | this.onError(new this.Error(message)); 24 | }, 25 | 26 | onError: function(err) { 27 | this.emit('error', err); 28 | } 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /lib/Pipe.js: -------------------------------------------------------------------------------- 1 | var stream = require('stream'); 2 | var util = require('util'); 3 | 4 | 5 | Pipe = function() { 6 | }; 7 | 8 | util.inherits(Pipe, stream.Stream); 9 | 10 | Pipe.prototype.write = function(token) { 11 | this.emit('data', token); 12 | }; 13 | 14 | Pipe.prototype.end = function() { 15 | this.emit('end'); 16 | }; 17 | 18 | module.exports = Pipe; 19 | -------------------------------------------------------------------------------- /lib/Rule.js: -------------------------------------------------------------------------------- 1 | var CodePainterObject = require('./Object'); 2 | var util = require('./util'); 3 | 4 | 5 | function Rule() { 6 | Rule.super_.apply(this, arguments); 7 | } 8 | 9 | module.exports = util.inherits(Rule, CodePainterObject, { 10 | 11 | tokens: { 12 | space: {type: 'Whitespaces', value: ' '}, 13 | emptyString: {type: 'Whitespaces', value: ''} 14 | }, 15 | 16 | transform: function(input, settings, output) { 17 | this.input = input; 18 | this.settings = settings; 19 | this.output = output; 20 | this.validate(); 21 | this.tokens.EOL = {type: 'Whitespaces', value: this.EOL}; 22 | }, 23 | 24 | validate: function() { 25 | this.enforceRule = true; 26 | var keys = Object.keys(this.settings); 27 | for (var i = 0; i < keys.length; i++) { 28 | var key = keys[i]; 29 | var value = this.settings[key]; 30 | var supported = this.supportedSettings[key]; 31 | if (!supported) { 32 | continue; 33 | } 34 | var enforceRule = (typeof supported === 'function') ? 35 | supported(value) : 36 | supported.indexOf(value) !== -1; 37 | if (!enforceRule) { 38 | this.skipRule(); 39 | break; 40 | } 41 | } 42 | }, 43 | 44 | skipRule: function() { 45 | this.enforceRule = false; 46 | this.input.on('data', function(token) { 47 | this.output.write(token); 48 | }.bind(this)); 49 | this.input.on('end', function() { 50 | this.output.end(); 51 | }.bind(this)); 52 | }, 53 | 54 | onTransformEnd: function() { 55 | this.output.end(); 56 | }, 57 | 58 | endsWithNewline: function(token) { 59 | return token.value.substr(-1, 1) === '\n'; 60 | }, 61 | 62 | hasNewline: function(token) { 63 | return this.isWhitespaces(token) && token.value.indexOf('\n') !== -1; 64 | }, 65 | 66 | isCloseCurlyBrace: function(token) { 67 | return this.isPunctuator(token) && token.value === '}'; 68 | }, 69 | 70 | isFunctionKeyword: function(token) { 71 | return token && token.type === 'Keyword' && token.value === 'function'; 72 | }, 73 | 74 | isIdentifier: function(token) { 75 | return token && token.type === 'Identifier'; 76 | }, 77 | 78 | isLineComment: function(token) { 79 | return token && token.type === 'LineComment'; 80 | }, 81 | 82 | isOnlySpaces: function(token) { 83 | return this.isWhitespaces(token) && /^ +$/.test(token.value); 84 | }, 85 | 86 | isOpenCurlyBrace: function(token) { 87 | return this.isPunctuator(token) && token.value === '{'; 88 | }, 89 | 90 | isOpenParen: function(token) { 91 | return this.isPunctuator(token) && token.value === '('; 92 | }, 93 | 94 | isCloseParen: function(token) { 95 | return this.isPunctuator(token) && token.value === ')'; 96 | }, 97 | 98 | isPunctuator: function(token) { 99 | return token && token.type === 'Punctuator'; 100 | }, 101 | 102 | isWhitespaces: function(token) { 103 | return token && token.type === 'Whitespaces'; 104 | }, 105 | 106 | isWhitespacesSansNewline: function(token) { 107 | return this.isWhitespaces(token) && !this.hasNewline(token); 108 | } 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /lib/Serializer.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Stream = require('stream').Stream; 3 | var util = require('util'); 4 | 5 | 6 | Serializer = function() { 7 | this.readable = true; 8 | this.writable = true; 9 | }; 10 | 11 | util.inherits(Serializer, Stream); 12 | 13 | Serializer.prototype.write = function(token) { 14 | var string = null; 15 | switch (token.type) { 16 | case 'Boolean': 17 | case 'Identifier': 18 | case 'Keyword': 19 | case 'Null': 20 | case 'Numeric': 21 | case 'Punctuator': 22 | case 'RegularExpression': 23 | case 'String': 24 | case 'Whitespaces': 25 | string = token.value; 26 | break; 27 | case 'BlockComment': 28 | string = '/*' + token.value + '*/'; 29 | break; 30 | case 'LineComment': 31 | string = '//' + token.value; 32 | break; 33 | } 34 | 35 | assert(string !== null, token.type); 36 | this.emit('data', string); 37 | }; 38 | 39 | Serializer.prototype.end = function() { 40 | this.emit('end'); 41 | }; 42 | 43 | module.exports = Serializer; 44 | -------------------------------------------------------------------------------- /lib/Tokenizer.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Stream = require('stream').Stream; 3 | var util = require('util'); 4 | var esprima = require('esprima'); 5 | var os = require('os'); 6 | 7 | 8 | Tokenizer = function() { 9 | this.contents = ''; 10 | this.writable = true; 11 | this.readable = true; 12 | this.rules = []; 13 | }; 14 | 15 | util.inherits(Tokenizer, Stream); 16 | 17 | Tokenizer.prototype.registerRules = function(rules) { 18 | this.rules = rules; 19 | }; 20 | 21 | Tokenizer.prototype.write = function(string) { 22 | this.contents += string; 23 | }; 24 | 25 | Tokenizer.prototype.end = function() { 26 | try { 27 | var parsed = esprima.parse(this.contents, { 28 | comment: true, 29 | range: true, 30 | tokens: true, 31 | loc: true, 32 | tolerant: true 33 | }); 34 | } catch(err) { 35 | err.message = [ 36 | 'Unable to parse JavaScript with Esprima.', 37 | err.message + '.', 38 | 'Are you trying to parse ES6 Harmony code? Use CodePainter\'s harmony branch.' 39 | ].join(os.EOL); 40 | this.emit('error', err); 41 | return; 42 | } 43 | 44 | this.rules.forEach(function(rule) { 45 | if (typeof rule.prepare === 'function') { 46 | rule.prepare(parsed, this.contents); 47 | } 48 | }.bind(this)); 49 | 50 | var c = 0, 51 | body = parsed.body, 52 | comments = parsed.comments, 53 | t = 0, 54 | tokens = parsed.tokens, 55 | end = this.contents.length, 56 | pos = 0; 57 | 58 | /** 59 | * Recursively walks the parser tree to find the grammar element for a given 60 | * syntax token 61 | */ 62 | function findGrammarToken(syntaxToken, tree) { 63 | var elem; 64 | 65 | for (var key in tree) { 66 | 67 | var tk = tree[key]; 68 | 69 | if (!(tk instanceof Object) || (key === 'range')) { 70 | continue; 71 | } 72 | 73 | var tkr = tk['range']; 74 | 75 | if (tkr === undefined) { 76 | elem = findGrammarToken(syntaxToken, tk); 77 | if (elem === null) { 78 | continue; 79 | } 80 | return elem; 81 | } 82 | 83 | var str = syntaxToken['range']; 84 | 85 | // tree element has a range 86 | if (str[0] >= tkr[0] && str[1] <= tkr[1]) {// token is within range of tree element 87 | elem = findGrammarToken(syntaxToken, tk); 88 | return (elem === null) ? tk : elem; 89 | } 90 | 91 | } 92 | return null; 93 | } 94 | 95 | function tokensLeft() { 96 | return pos < end; 97 | } 98 | 99 | function nextToken() { 100 | var comment = c < comments.length ? comments[c] : null, 101 | commentPos = comment ? comment.range[0] : end, 102 | token = t < tokens.length ? tokens[t] : null, 103 | tokenPos = token ? token.range[0] : end, 104 | nextPos = Math.min(commentPos, tokenPos), 105 | outToken; 106 | 107 | assert(pos <= nextPos); 108 | // There are whitespaces between the previous and the next token. 109 | // FIXME: It would be cool to detach whitespaces from the stream but still make it super 110 | // easy for the pipeline to ask for the whitespaces that follow or precede a specific token. 111 | if (pos < nextPos) { 112 | var whitespaces = this.contents.substring(pos, nextPos); 113 | outToken = { 114 | type: 'Whitespaces', 115 | value: whitespaces, 116 | range: [pos, nextPos] 117 | }; 118 | pos = nextPos; 119 | } else if (commentPos < tokenPos) { 120 | c++; 121 | pos = comment.range[1]; 122 | var commentType = comment.type === 'Line' ? 'LineComment' : 'BlockComment'; 123 | outToken = { 124 | type: commentType, 125 | value: comment.value, 126 | range: comment.range 127 | }; 128 | } else { 129 | t++; 130 | pos = token.range[1]; 131 | outToken = { 132 | type: token.type, 133 | value: token.value, 134 | range: token.range 135 | }; 136 | } 137 | 138 | outToken.grammarToken = findGrammarToken(outToken, body); 139 | 140 | return outToken; 141 | } 142 | 143 | while (tokensLeft()) { 144 | var token = nextToken.call(this); 145 | this.emit('data', token); 146 | } 147 | this.emit('end'); 148 | }; 149 | 150 | module.exports = Tokenizer; 151 | -------------------------------------------------------------------------------- /lib/Transformer.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var editorconfig = require('editorconfig'); 3 | var extend = require('node.extend'); 4 | var fs = require('fs'); 5 | var glob = require('glob'); 6 | var mkdirp = require('mkdirp'); 7 | var MemoryStream = require('memorystream'); 8 | var path = require('path'); 9 | var stream = require('stream'); 10 | var util = require('util'); 11 | 12 | var codepainter = require('../codepainter'); 13 | var CodePainterObject = require('./Object'); 14 | var EndOfLineRule = require('./rules/end_of_line'); 15 | var Inferrer = require('./Inferrer'); 16 | var rules = require('./rules'); 17 | var Serializer = require('./Serializer'); 18 | var Tokenizer = require('./Tokenizer'); 19 | var util = require('./util'); 20 | 21 | 22 | function Transformer() { 23 | Transformer.super_.apply(this, arguments); 24 | } 25 | 26 | module.exports = util.inherits(Transformer, CodePainterObject, { 27 | 28 | transform: function(globs, options, callback) { 29 | 30 | if (typeof options === 'function') { 31 | callback = options; 32 | options = undefined; 33 | } 34 | 35 | this.globs = globs; 36 | this.options = options || {}; 37 | this.callback = callback; 38 | 39 | this.transformed = 0; 40 | this.skipped = 0; 41 | this.errored = 0; 42 | this.style = {}; 43 | 44 | if (options && options.infer) { 45 | this.infer(this.cascadeAndTransform.bind(this)); 46 | return; 47 | } 48 | 49 | this.cascadeAndTransform(); 50 | }, 51 | 52 | infer: function(callback) { 53 | codepainter.infer(this.options.infer, function(inferredStyle) { 54 | this.emit('cascade', this.style, inferredStyle, 'Inferred style'); 55 | this.style = inferredStyle; 56 | callback(); 57 | }.bind(this)); 58 | }, 59 | 60 | cascadeAndTransform: function() { 61 | this.cascadeStyles(function() { 62 | var globs = this.globs; 63 | if (typeof globs === 'string') { 64 | this.inputs = [globs]; 65 | this.transformPath(globs); 66 | } else if (globs && globs instanceof stream.Readable) { 67 | this.transformStream(globs); 68 | } else if (globs && globs[0] instanceof stream.Readable) { 69 | this.transformStream(globs[0]); 70 | } else { 71 | util.reduceGlobs(globs, this.onGlobPaths.bind(this)); 72 | } 73 | }.bind(this)); 74 | }, 75 | 76 | cascadeStyles: function(cb) { 77 | var style = this.style; 78 | var options = this.options; 79 | 80 | cascadePredef.call(this); 81 | 82 | function cascadePredef() { 83 | if (!options.predef) { 84 | cascadeJson.call(this); 85 | return; 86 | } 87 | fs.readFile(__dirname + '/styles/' + options.predef + '.json', 'utf8', function(err, data) { 88 | if (err) { 89 | this.err = err; 90 | return; 91 | } 92 | data = JSON.parse(data); 93 | this.emit('cascade', style, data, options.predef + ' style'); 94 | style = extend(style, data); 95 | cascadeJson.call(this); 96 | }.bind(this)); 97 | } 98 | 99 | function cascadeJson() { 100 | if (!options.json) { 101 | cascadeInline.call(this); 102 | return; 103 | } 104 | fs.readFile(path.resolve(options.json), 'utf8', function(err, data) { 105 | if (err) { 106 | this.err = err; 107 | return; 108 | } 109 | data = JSON.parse(data); 110 | this.emit('cascade', style, data, 'Supplied JSON file'); 111 | style = extend(style, data); 112 | cascadeInline.call(this); 113 | }.bind(this)); 114 | } 115 | 116 | function cascadeInline() { 117 | if (options.style && Object.keys(options.style).length) { 118 | this.emit('cascade', style, options.style, 'Inline styles'); 119 | style = extend(style, options.style); 120 | } 121 | cascadeEditorConfig.call(this); 122 | } 123 | 124 | function cascadeEditorConfig() { 125 | if (options.editorConfig) { 126 | this.emit('cascade', style, { 127 | editor_config: true 128 | }, 'Editor Config (applied on a file-by-file basis)'); 129 | } 130 | applyStyle.call(this); 131 | } 132 | 133 | function applyStyle() { 134 | this.style = style; 135 | cb(); 136 | } 137 | 138 | }, 139 | 140 | transformPath: function(inputPath, outputPath) { 141 | var style = this.cascadeEditorConfigStyle(inputPath); 142 | var rules = this.getRules(style); 143 | 144 | if (!rules.length || style.codepaint === false) { 145 | this.skip(inputPath); 146 | return; 147 | } 148 | 149 | var inputStream = this.createInputStream(inputPath); 150 | if (typeof this.options.output === 'string') { 151 | outputPath = this.options.output; 152 | } 153 | var output = outputPath ? MemoryStream.createWriteStream() : 154 | this.options.output || process.stdout; 155 | var streams = this.createStreams(inputStream, rules, output); 156 | 157 | new Inferrer().infer(inputPath, function(inferredStyle) { 158 | this.EOL = this.getEOLChar(inferredStyle['end_of_line']); 159 | this.applyRules(rules, streams, style); 160 | inputStream.resume(); 161 | }.bind(this), EndOfLineRule); 162 | 163 | inputStream.on('end', this.writeOutput.bind(this, outputPath, output)); 164 | }, 165 | 166 | getEOLChar: function(setting) { 167 | switch (setting) { 168 | case 'crlf': 169 | return '\r\n'; 170 | case 'lf': 171 | return '\n'; 172 | default: 173 | return os.EOL; 174 | } 175 | }, 176 | 177 | writeOutput: function(outputPath, output) { 178 | if (!outputPath) { 179 | this.onClose(); 180 | return; 181 | } 182 | mkdirp(path.dirname(outputPath), function(err) { 183 | if (err) { 184 | this.err = err; 185 | this.onClose(); 186 | return; 187 | } 188 | fs.writeFile(outputPath, output.toString(), this.onClose.bind(this)); 189 | }.bind(this)); 190 | }, 191 | 192 | onClose: function() { 193 | this.closed = this.closed || 0; 194 | if (++this.closed !== ++this.transformed + this.errored) { 195 | console.log('returning'); 196 | return; 197 | } 198 | this.emit('end', this.err, this.transformed, this.skipped, this.errored); 199 | if (typeof this.callback === 'function') { 200 | this.callback(this.err, this.transformed, this.skipped, this.errored); 201 | } 202 | }, 203 | 204 | transformStream: function(inputStream) { 205 | inputStream.setEncoding('utf8'); 206 | var rules = this.getRules(this.style); 207 | if (!rules.length) { 208 | inputStream.on('data', function(chunk) { 209 | process.stdout.write(chunk); 210 | }); 211 | } 212 | 213 | var data = ''; 214 | inputStream.on('data', function(chunk) { 215 | data += chunk; 216 | }); 217 | 218 | new EndOfLineRule().infer(inputStream, function(inferredStyle) { 219 | this.EOL = this.getEOLChar(inferredStyle['end_of_line']); 220 | var memoryStream = MemoryStream.createReadStream(data); 221 | var streams = this.createStreams(memoryStream, rules); 222 | this.applyRules(rules, streams, this.style); 223 | }.bind(this)); 224 | }, 225 | 226 | onGlobPaths: function(paths) { 227 | this.inputs = paths; 228 | paths.forEach(function(path) { 229 | if (fs.statSync(path).isFile()) { 230 | this.transformPath(path, path); 231 | } 232 | }.bind(this)); 233 | }, 234 | 235 | skip: function(inputPath) { 236 | this.emit('skip', ++this.skipped, inputPath); 237 | this.inputEnd(); 238 | }, 239 | 240 | cascadeEditorConfigStyle: function(inputPath) { 241 | var style = extend({}, this.style); 242 | if (this.options.editorConfig) { 243 | var editorConfigStyle = editorconfig.parse(inputPath); 244 | style = extend(style, editorConfigStyle); 245 | } 246 | return style; 247 | }, 248 | 249 | getRules: function(style) { 250 | var rulesToApply = []; 251 | rules.forEach(function(Rule) { 252 | var supportedSettings = Rule.prototype.supportedSettings; 253 | var keys = Object.keys(supportedSettings); 254 | for (var i = 0; i < keys.length; i++) { 255 | var key = keys[i]; 256 | if (key in style) { 257 | rulesToApply.push(new Rule()); 258 | break; 259 | } 260 | } 261 | }.bind(this)); 262 | return rulesToApply; 263 | }, 264 | 265 | createInputStream: function(inputPath) { 266 | var stream = fs.createReadStream(inputPath); 267 | stream.pause(); 268 | stream.setEncoding('utf8'); 269 | return stream; 270 | }, 271 | 272 | createStreams: function(input, rules, outputStream) { 273 | var tokenizer = new Tokenizer(); 274 | input.pipe(tokenizer); 275 | 276 | var inputPath = input.path || path.resolve('.'); 277 | var output = outputStream || process.stdout; 278 | 279 | tokenizer.on('error', function(err) { 280 | this.onError.call(this, err, inputPath); 281 | }.bind(this)); 282 | 283 | var serializer = new Serializer(); 284 | serializer.pipe(output); 285 | 286 | output.on('end', this.onTransformEnd.bind(this, inputPath)); 287 | 288 | tokenizer.registerRules(rules); 289 | 290 | var streams = [tokenizer]; 291 | for (var i = 0; i < rules.length - 1; i++) 292 | streams.push(new Pipe()); 293 | 294 | streams.push(serializer); 295 | return streams; 296 | }, 297 | 298 | applyRules: function(rules, streams, style) { 299 | rules.forEach(function(rule, i) { 300 | rule.EOL = this.EOL; 301 | rule.transform(streams[i], style, streams[i + 1], onError); 302 | }.bind(this)); 303 | function onError() {} 304 | }, 305 | 306 | onTransformEnd: function(inputPath) { 307 | this.emit('transform', ++this.transformed, inputPath); 308 | this.inputEnd(); 309 | }, 310 | 311 | inputEnd: function(err) { 312 | this.processed = this.transformed + this.skipped + this.errored; 313 | if (this.processed !== this.inputs.length) { 314 | return; 315 | } 316 | this.err = this.firstError || err; 317 | }, 318 | 319 | onError: function(err, inputPath) { 320 | this.emit('error', err, inputPath); 321 | if (!this.firstError) { 322 | this.firstError = err; 323 | } 324 | this.errored++; 325 | this.inputEnd.call(this, err); 326 | } 327 | 328 | }); 329 | -------------------------------------------------------------------------------- /lib/rules/end_of_line.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function EndOfLineRule() { 8 | EndOfLineRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(EndOfLineRule, Rule, { 12 | 13 | name: 'end_of_line', 14 | 15 | supportedSettings: { 16 | end_of_line: ['crlf', 'lf'] 17 | }, 18 | 19 | infer: function(sample, callback) { 20 | this.callback = callback; 21 | this.lfs = 0; 22 | this.crlfs = 0; 23 | 24 | sample.on('data', this.onInferData.bind(this)); 25 | sample.on('end', this.onInferEnd.bind(this)); 26 | }, 27 | 28 | onInferData: function(token) { 29 | if (!this.isWhitespaces(token)) { 30 | return; 31 | } 32 | 33 | var eols = token.value.match(/\r?\n/g); 34 | if (!eols) { 35 | return; 36 | } 37 | 38 | var crlfs = token.value.match(/\r\n/g); 39 | crlfs = crlfs && crlfs.length || 0; 40 | this.crlfs += crlfs; 41 | 42 | this.lfs += eols.length - crlfs; 43 | }, 44 | 45 | onInferEnd: function() { 46 | this.callback({ 47 | end_of_line: (this.crlfs > this.lfs) ? 'crlf' : 'lf' 48 | }); 49 | }, 50 | 51 | transform: function(input) { 52 | Rule.prototype.transform.apply(this, arguments); 53 | if (!this.enforceRule) { 54 | return; 55 | } 56 | this.EOL = (this.settings['end_of_line'] === 'lf') ? '\n' : '\r\n'; 57 | input.on('data', this.onTransformData.bind(this)); 58 | input.on('end', this.onTransformEnd.bind(this)); 59 | }, 60 | 61 | onTransformData: function(token) { 62 | if (this.hasNewline(token)) { 63 | token.value = token.value.replace(/\r?\n/g, this.EOL); 64 | } 65 | this.output.write(token); 66 | } 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /lib/rules/indent_style_and_size.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function IndentStyleAndSizeRule() { 8 | IndentStyleAndSizeRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(IndentStyleAndSizeRule, Rule, { 12 | 13 | name: 'indent_style_and_size', 14 | 15 | supportedSettings: { 16 | indent_style: ['tab', 'space'], 17 | indent_size: function(value) { 18 | return!isNaN(value); 19 | } 20 | }, 21 | 22 | noIndenting: [ 23 | 'BlockStatement', 24 | 'Program', 25 | 'NewExpression', 26 | 'VariableDeclarator', 27 | 'CallExpression', 28 | 'FunctionExpression', 29 | 'ObjectExpression' 30 | ], 31 | 32 | postfixExpressions: [ 33 | 'LogicalExpression', 34 | 'BinaryExpression', 35 | 'UnaryExpression' 36 | ], 37 | 38 | prepare: function(tree, contents) { 39 | this.noIndenting += this.postfixExpressions; 40 | this.annotateGrammarTree(tree, tree,-1, [0, contents.length], ''); 41 | }, 42 | 43 | annotateGrammarTree: function(tree, parent, parentLevel, parentRange, parentPath, parentLoc) { 44 | var level; 45 | var path; 46 | var range, loc; 47 | 48 | if (tree['range'] !== undefined) {// tree element has a range 49 | 50 | if (tree['range'][0] === parentRange[0] && parentPath !== '-Program' || // token is first token of parent token 51 | this.noIndenting.indexOf(tree['type']) !== -1 || // token is on the blacklist 52 | this.postfixExpressions.indexOf(parent['type']) !== -1 || 53 | tree['type'] === 'IfStatement' && parent['type'] === 'IfStatement') { 54 | 55 | // do not indent 56 | level = parentLevel; 57 | path = parentPath + '-' + tree['type']; 58 | range = tree['range']; 59 | loc = tree['loc']; 60 | 61 | } else { 62 | 63 | level = parentLevel + 1; 64 | path = parentPath + '-' + tree['type'] + '+1'; 65 | range = tree['range']; 66 | loc = tree['loc']; 67 | } 68 | 69 | tree['Indentation'] = tree['Indentation'] || {}; 70 | tree['Indentation']['level'] = level; 71 | tree['Indentation']['path'] = path; 72 | tree['Indentation']['parent'] = parent.type; 73 | 74 | } else { 75 | level = parentLevel; 76 | path = parentPath; 77 | range = parentRange; 78 | loc = parentLoc; 79 | } 80 | 81 | for (var key in tree) { 82 | if (!(tree[key] instanceof Object) || (key === 'range')) { 83 | continue; 84 | } 85 | this.annotateGrammarTree(tree[key], tree, level, range, path, loc); 86 | } 87 | }, 88 | 89 | infer: function(sample, callback) { 90 | this.callback = callback; 91 | 92 | this.characters = {}; 93 | this.indent = 0; 94 | this.totalCount = 0; 95 | this.prevToken = null; 96 | 97 | sample.on('data', this.onInferData.bind(this)); 98 | sample.on('end', this.onInferEnd.bind(this)); 99 | }, 100 | 101 | onInferData: function(token) { 102 | 103 | // FIXME: Handle if/for/while one-liners. 104 | // FIXME: Fix function argument/variable declaration alignment. 105 | if (this.isCloseCurlyBrace(token)) 106 | this.indent--; 107 | if (this.isWhitespaces(this.prevToken) && this.indent > 0) 108 | this.processWhitespaces(this.prevToken); 109 | if (this.isOpenCurlyBrace(token)) 110 | this.indent++; 111 | 112 | this.prevToken = token; 113 | }, 114 | 115 | processWhitespaces: function(token) { 116 | var value = token.value; 117 | var newLinePos = value.lastIndexOf('\n'); 118 | if (newLinePos === -1 || newLinePos === value.length - 1) 119 | return; 120 | value = value.substr(newLinePos + 1); 121 | 122 | var indentationType = this.indentation(value); 123 | if (indentationType) { 124 | var character = indentationType.character; 125 | var count = indentationType.count; 126 | if (typeof this.characters[character] === 'undefined') 127 | this.characters[character] = []; 128 | if (typeof this.characters[character][count] === 'undefined') 129 | this.characters[character][count] = 0; 130 | this.characters[character][count]++; 131 | } 132 | this.totalCount++; 133 | }, 134 | 135 | onInferEnd: function() { 136 | var max = 0, 137 | mostCommon = {}, 138 | sum = 0, 139 | value = null, 140 | fn = function(count, index) { 141 | if (count > max) { 142 | max = count; 143 | mostCommon = {character: this.toString(), width: index}; 144 | } 145 | sum += count; 146 | }; 147 | for (var character in this.characters) { 148 | this.characters[character].forEach(fn, character); 149 | } 150 | 151 | if (max > this.totalCount - sum) 152 | value = mostCommon; 153 | 154 | var settings = {}; 155 | if (value && value.character === '\t') { 156 | settings.indent_style = 'tab'; 157 | } else { 158 | settings.indent_style = 'space'; 159 | settings.indent_size = value && value.width || 4; 160 | } 161 | 162 | this.callback(settings); 163 | }, 164 | 165 | indentation: function(whitespaces) { 166 | var first = whitespaces[0]; 167 | if (!Array.prototype.every.call(whitespaces, function(character) { 168 | return character === first; 169 | })) 170 | return null; 171 | return { 172 | character: first, 173 | count: Math.floor(whitespaces.length / this.indent) 174 | }; 175 | }, 176 | 177 | transform: function(input, settings) { 178 | Rule.prototype.transform.apply(this, arguments); 179 | if (!this.enforceRule) { 180 | return; 181 | } 182 | 183 | this.prevToken = null; 184 | this.oneIndent = (settings.indent_style === 'tab') ? 185 | '\t' : 186 | ' '.repeat(settings.indent_size); 187 | 188 | input.on('data', this.onTransformData.bind(this)); 189 | input.on('end', this.onTransformEnd.bind(this)); 190 | }, 191 | 192 | onTransformData: function(token) { 193 | 194 | var prevToken = this.prevToken; 195 | 196 | if (prevToken === null && this.isWhitespaces(token)) { 197 | token.value = ''; 198 | } 199 | 200 | if (prevToken === null || this.hasNewline(prevToken)) { 201 | 202 | if (prevToken === null) { 203 | prevToken = this.prevToken = this.tokens.emptyString; 204 | } 205 | 206 | var indentLevel = 0; 207 | //var path; 208 | 209 | //token has a grammarToken and is not an unindented line comment (i.e. commented out code) 210 | if (token.grammarToken && 211 | !(this.isLineComment(token) && (prevToken.value === '' || this.endsWithNewline(prevToken)))) { 212 | indentLevel = token.grammarToken['Indentation'].level; 213 | //path = token.grammarToken[ 'Indentation' ].path; 214 | 215 | if (token.range[0] !== token.grammarToken.range[0] && 216 | token.range[1] !== token.grammarToken.range[1] && 217 | ['(', ')', '.', 'else'].indexOf(token.value) === -1 || 218 | this.postfixExpressions.indexOf(token.grammarToken['Indentation'].parent) !== -1) { 219 | indentLevel++; 220 | //path = path + '*'; 221 | } 222 | } 223 | 224 | var lineCount = (prevToken && prevToken.value.match(/\r?\n/g) || []).length; 225 | //prevToken.value = this.EOL.repeat(lineCount) + this.oneIndent.repeat(indent) + '/*' + (indent) + '(' + path + ')*/ '; 226 | prevToken.value = this.EOL.repeat(lineCount) + this.oneIndent.repeat(indentLevel); 227 | } 228 | 229 | this.output.write(prevToken); 230 | this.prevToken = token; 231 | }, 232 | 233 | onTransformEnd: function() { 234 | if (this.prevToken !== null) { 235 | this.output.write(this.prevToken); 236 | } 237 | this.output.end(); 238 | } 239 | 240 | }); 241 | -------------------------------------------------------------------------------- /lib/rules/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./indent_style_and_size'), 3 | require('./insert_final_newline'), 4 | require('./quote_type'), 5 | require('./space_after_anonymous_functions'), 6 | require('./space_after_control_statements'), 7 | require('./spaces_around_operators'), 8 | require('./trim_trailing_whitespace'), 9 | require('./spaces_in_brackets'), 10 | require('./end_of_line') 11 | ]; 12 | -------------------------------------------------------------------------------- /lib/rules/insert_final_newline.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function InsertFinalNewlineRule() { 8 | InsertFinalNewlineRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(InsertFinalNewlineRule, Rule, { 12 | 13 | name: 'insert_final_newline', 14 | 15 | supportedSettings: { 16 | insert_final_newline: [true, false] 17 | }, 18 | 19 | infer: function(sample, callback) { 20 | var previousToken = null; 21 | 22 | sample.on('data', function(token) { 23 | previousToken = token; 24 | }); 25 | 26 | sample.on('end', function() { 27 | 28 | var value; 29 | 30 | if ((previousToken && previousToken.type === 'Whitespaces') && 31 | (previousToken.value.indexOf('\n') !== -1)) { 32 | 33 | value = true; 34 | } else { 35 | value = false; 36 | } 37 | 38 | callback({insert_final_newline: value}); 39 | }); 40 | }, 41 | 42 | transform: function(input) { 43 | Rule.prototype.transform.apply(this, arguments); 44 | if (!this.enforceRule) { 45 | return; 46 | } 47 | 48 | this.insertFinalNewline = this.settings['insert_final_newline']; 49 | this.prevToken = null; 50 | 51 | input.on('data', this.onTransformData.bind(this)); 52 | input.on('end', this.onTransformEnd.bind(this)); 53 | }, 54 | 55 | onTransformData: function(token) { 56 | if (this.prevToken) { 57 | this.output.write(this.prevToken); 58 | } 59 | 60 | this.prevToken = token; 61 | }, 62 | 63 | onTransformEnd: function() { 64 | var output = this.output; 65 | var token = this.prevToken; 66 | 67 | if (!this.insertFinalNewline) { 68 | while (this.hasFinalNewline(token)) { 69 | token.value = token.value.replace(/\r?\n$/, ''); 70 | } 71 | } 72 | 73 | output.write(token); 74 | 75 | if (this.insertFinalNewline && !this.hasFinalNewline(token)) { 76 | output.write(this.tokens.EOL); 77 | } 78 | 79 | output.end(); 80 | }, 81 | 82 | hasFinalNewline: function(token) { 83 | return this.isWhitespaces(token) && /\r?\n$/.test(token.value); 84 | } 85 | 86 | }); 87 | -------------------------------------------------------------------------------- /lib/rules/quote_type.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | string = require('../util/string'); 3 | 4 | var Rule = require('../Rule'); 5 | var util = require('../util'); 6 | 7 | 8 | function QuoteTypeRule() { 9 | QuoteTypeRule.super_.apply(this, arguments); 10 | } 11 | 12 | module.exports = util.inherits(QuoteTypeRule, Rule, { 13 | 14 | name: 'quote_type', 15 | 16 | supportedSettings: { 17 | quote_type: ['single', 'double', 'auto'] 18 | }, 19 | 20 | quoteMap: { 21 | 'double': '"', 22 | 'single': "'" 23 | }, 24 | 25 | typeMap: { 26 | '"': 'double', 27 | "'": 'single' 28 | }, 29 | 30 | regexMap: { 31 | 'double': { 32 | escaped: /"\\/g, 33 | unescaped: /("(\\\\)*)(?!\\)/g 34 | }, 35 | 'single': { 36 | escaped: /'\\/g, 37 | unescaped: /(\'(\\\\)*)(?!\\)/g 38 | } 39 | }, 40 | 41 | infer: function(sample, callback) { 42 | var doubleQuotes = 0, 43 | totalCount = 0; 44 | 45 | sample.on('data', function(token) { 46 | if (token.type === 'String') { 47 | totalCount++; 48 | if (token.value[0] === '"') { 49 | doubleQuotes++; 50 | } 51 | } 52 | }.bind(this)); 53 | 54 | sample.on('end', function() { 55 | var singleQuotes = totalCount - doubleQuotes; 56 | var style = {}; 57 | style[this.name] = function() { 58 | if (doubleQuotes > 0 && singleQuotes === 0) { 59 | return 'double'; 60 | } else if (singleQuotes > 0 && doubleQuotes === 0) { 61 | return 'single'; 62 | } else { 63 | return 'auto'; 64 | } 65 | }(); 66 | callback(style); 67 | }.bind(this)); 68 | }, 69 | 70 | transform: function(input, settings, output) { 71 | Rule.prototype.transform.apply(this, arguments); 72 | if (!this.enforceRule) { 73 | return; 74 | } 75 | 76 | this.quoteType = settings['quote_type']; 77 | 78 | input.on('data', this.onTransformData.bind(this)); 79 | 80 | input.on('end', function() { 81 | output.end(); 82 | }.bind(this)); 83 | }, 84 | 85 | onTransformData: function(token) { 86 | if (token.type === 'String') { 87 | token.value = this.changeQuotes(token.value); 88 | } 89 | this.output.write(token); 90 | }, 91 | 92 | changeQuotes: function(string) { 93 | var currentLiteral = string[0]; 94 | var currentType = this.typeMap[currentLiteral]; 95 | 96 | if (this.quoteType === currentType) { 97 | return string; 98 | } 99 | 100 | var value = string.substring(1, string.length - 1).reverse(); 101 | 102 | var newQuoteType = this.quoteType; 103 | if (this.quoteType === 'auto') { 104 | newQuoteType = this.inferLiteral(value); 105 | } 106 | 107 | var settingLiteral = this.quoteMap[newQuoteType]; 108 | 109 | // Remove redundant escaping. 110 | value = value.replace(this.regexMap[currentType].escaped, currentLiteral); 111 | // Add new escaping. 112 | value = value.replace(this.regexMap[newQuoteType].unescaped, settingLiteral + '\\'); 113 | return settingLiteral + value.reverse() + settingLiteral; 114 | }, 115 | 116 | inferLiteral: function(string) { 117 | var m = string.match(/"/g); 118 | var doubles = m && m.length; 119 | m = string.match(/'/g); 120 | var singles = m && m.length; 121 | return (singles && singles > doubles) ? 'double' : 'single'; 122 | } 123 | 124 | }); 125 | -------------------------------------------------------------------------------- /lib/rules/space_after_anonymous_functions.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function SpaceAfterAnonymousFunctionsRule() { 8 | SpaceAfterAnonymousFunctionsRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(SpaceAfterAnonymousFunctionsRule, Rule, { 12 | 13 | name: 'space_after_anonymous_functions', 14 | 15 | supportedSettings: { 16 | space_after_anonymous_functions: [true, false] 17 | }, 18 | 19 | infer: function(sample, callback) { 20 | var previousTokens = new Array(2); 21 | var present = 0; 22 | var omitted = 0; 23 | 24 | sample.on('data', function(token) { 25 | if (this.isFunctionKeyword(previousTokens[0])) { 26 | if (this.isOpenParen(previousTokens[1])) { 27 | omitted++; 28 | } else { 29 | assert(this.isWhitespaces(previousTokens[1])); 30 | if (this.isOpenParen(token)) { 31 | // Anonymous function. 32 | if (previousTokens[1].value === ' ') { 33 | present++; 34 | } 35 | } else { 36 | // Named function. 37 | assert(this.isIdentifier(token)); 38 | } 39 | } 40 | } 41 | previousTokens.shift(); 42 | previousTokens.push(token); 43 | }.bind(this)); 44 | 45 | sample.on('end', function() { 46 | callback({space_after_anonymous_functions: present > omitted}); 47 | }); 48 | }, 49 | 50 | transform: function(input) { 51 | Rule.prototype.transform.apply(this, arguments); 52 | if (!this.enforceRule) { 53 | return; 54 | } 55 | 56 | this.insertSpace = this.settings['space_after_anonymous_functions']; 57 | this.removeSpace = !this.insertSpace; 58 | 59 | this.prevTokens = new Array(2); 60 | input.on('data', this.onTransformData.bind(this)); 61 | input.on('end', this.onTransformEnd.bind(this)); 62 | }, 63 | 64 | onTransformData: function(token) { 65 | if (this.isFunctionKeyword(this.prevTokens[1])) { 66 | if (this.isOpenParen(token)) { 67 | if (this.insertSpace) 68 | this.output.write(this.tokens.space); 69 | this.output.write(token); 70 | } else { 71 | assert(this.isWhitespaces(token)); 72 | // Omit until we know if it's an anonymous function. 73 | } 74 | } else if (this.isFunctionKeyword(this.prevTokens[0]) && 75 | this.isWhitespaces(this.prevTokens[1])) { 76 | 77 | if (this.isOpenParen(token)) { 78 | // Anonymous function. 79 | if (this.insertSpace) 80 | this.output.write(this.tokens.space); 81 | } else { 82 | // Named function. 83 | assert(this.isIdentifier(token)); 84 | this.output.write(this.prevTokens[1]); 85 | } 86 | this.output.write(token); 87 | } else { 88 | this.output.write(token); 89 | } 90 | this.prevTokens.shift(); 91 | this.prevTokens.push(token); 92 | } 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /lib/rules/space_after_control_statements.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function SpaceAfterControlStatementsRule() { 8 | SpaceAfterControlStatementsRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(SpaceAfterControlStatementsRule, Rule, { 12 | 13 | name: 'space_after_control_statements', 14 | 15 | supportedSettings: { 16 | space_after_control_statements: [true, false] 17 | }, 18 | 19 | controlKeywords: ['if', 'for', 'switch', 'while', 'with'], 20 | 21 | infer: function(sample, callback) { 22 | 23 | this.callback = callback; 24 | 25 | this.prevToken = null; 26 | this.trueTrend = 0; 27 | this.falseTrend = 0; 28 | 29 | sample.on('data', this.onInferData.bind(this)); 30 | sample.on('end', this.onInferEnd.bind(this)); 31 | }, 32 | 33 | onInferData: function(token) { 34 | this.token = token; 35 | if (this.isTrueStyle()) { 36 | this.trueTrend++; 37 | } else if (this.isFalseStyle()) { 38 | this.falseTrend++; 39 | } 40 | this.prevToken = token; 41 | }, 42 | 43 | isTrueStyle: function() { 44 | return this.isControlKeyword(this.prevToken) && this.isWhitespaces(this.token); 45 | }, 46 | 47 | isFalseStyle: function() { 48 | return this.isControlKeyword(this.prevToken) && !this.isWhitespaces(this.token); 49 | }, 50 | 51 | onInferEnd: function() { 52 | var t = this.trueTrend; 53 | var f = this.falseTrend; 54 | var setting = (t > f) ? true : (f > t) ? false : null; 55 | this.callback({space_after_control_statements: setting}); 56 | }, 57 | 58 | transform: function(input) { 59 | Rule.prototype.transform.apply(this, arguments); 60 | if (!this.enforceRule) { 61 | return; 62 | } 63 | 64 | this.prevToken = null; 65 | 66 | switch (this.settings['space_after_control_statements']) { 67 | case true: 68 | input.on('data', this.onTrueTransformData.bind(this)); 69 | break; 70 | case false: 71 | input.on('data', this.onFalseTransformData.bind(this)); 72 | break; 73 | } 74 | 75 | input.on('end', this.onTransformEnd.bind(this)); 76 | }, 77 | 78 | onTrueTransformData: function(token) { 79 | if (this.isControlKeyword(this.prevToken)) { 80 | this.output.write(this.tokens.space); 81 | if (!this.isWhitespaces(token)) { 82 | assert(this.isOpenParen(token)); 83 | this.output.write(token); 84 | } 85 | } else { 86 | this.output.write(token); 87 | } 88 | this.prevToken = token; 89 | }, 90 | 91 | onFalseTransformData: function(token) { 92 | if (!this.isControlKeyword(this.prevToken)) { 93 | this.output.write(token); 94 | this.prevToken = token; 95 | return; 96 | } 97 | if (!this.isWhitespaces(token)) { 98 | assert(this.isOpenParen(token)); 99 | this.output.write(token); 100 | } 101 | this.prevToken = token; 102 | }, 103 | 104 | isControlKeyword: function(token) { 105 | return token && token.type === 'Keyword' && this.controlKeywords.indexOf(token.value) !== -1; 106 | } 107 | 108 | }); 109 | -------------------------------------------------------------------------------- /lib/rules/spaces_around_operators.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function SpacesAroundOperatorsRule() { 8 | SpacesAroundOperatorsRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(SpacesAroundOperatorsRule, Rule, { 12 | 13 | name: 'spaces_around_operators', 14 | 15 | supportedSettings: { 16 | spaces_around_operators: [true, false, 'hybrid'] 17 | }, 18 | 19 | operators: [ 20 | '!', '~', 21 | '*', '/', '%', 22 | '+', '-', 23 | '<<', '>>', '>>>', 24 | '<', '<=', '>', '>=', 25 | '==', '!=', '===', '!==', 26 | '&', '^', '|', '&&', '||', '?', ':', 27 | '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=', '&=', '^=', '|=' 28 | ], 29 | 30 | hybridGroupOperators: ['*', '/', '%'], 31 | 32 | infer: function(sample, callback) { 33 | this.prevToken = null; 34 | this.trueTrend = 0; 35 | this.falseTrend = 0; 36 | this.callback = callback; 37 | 38 | sample.on('data', this.onInferData.bind(this)); 39 | sample.on('end', this.onInferEnd.bind(this)); 40 | }, 41 | 42 | onInferData: function(token) { 43 | this.token = token; 44 | if (!this.isException(token)) { 45 | if (this.isTrueStyle()) { 46 | this.trueTrend++; 47 | } else if (this.isFalseStyle()) { 48 | this.falseTrend++; 49 | } 50 | } 51 | this.prevToken = token; 52 | }, 53 | 54 | isTrueStyle: function() { 55 | return this.hasOperatorThenSpaces() || this.hasSpacesThenOperator(); 56 | }, 57 | 58 | isFalseStyle: function() { 59 | return this.isOperatorAdjacentToNonspace(); 60 | }, 61 | 62 | onInferEnd: function() { 63 | var setting; 64 | 65 | if (this.trueTrend > this.falseTrend) { 66 | setting = (this.falseTrend === 0) ? true : 'hybrid'; 67 | } else if (this.falseTrend > this.trueTrend) { 68 | setting = (this.trueTrend === 0) ? false : 'hybrid'; 69 | } else { 70 | setting = null; 71 | } 72 | 73 | this.callback({spaces_around_operators: setting}); 74 | }, 75 | 76 | transform: function(input) { 77 | Rule.prototype.transform.apply(this, arguments); 78 | if (!this.enforceRule) { 79 | return; 80 | } 81 | 82 | this.prevToken = null; 83 | 84 | switch (this.settings['spaces_around_operators']) { 85 | case true: 86 | input.on('data', this.onTrueTransformData.bind(this)); 87 | break; 88 | case false: 89 | input.on('data', this.onFalseTransformData.bind(this)); 90 | break; 91 | case 'hybrid': 92 | input.on('data', this.onHybridTransformData.bind(this)); 93 | break; 94 | } 95 | 96 | input.on('end', this.onTransformEnd.bind(this)); 97 | }, 98 | 99 | onTrueTransformData: function(token) { 100 | if (this.isNonConditionalColonOperator(token)) { 101 | this.onFalseTransformData(token); 102 | return; 103 | } 104 | 105 | this.token = token; 106 | var prevToken = this.prevToken; 107 | 108 | if (prevToken) { 109 | if (this.shouldRemoveTokenSpace()) { 110 | this.token.value = ''; 111 | } 112 | this.output.write(prevToken); 113 | } 114 | if (this.isOperatorAdjacentToNonspace()) { 115 | this.output.write(this.tokens.space); 116 | } 117 | this.prevToken = token; 118 | }, 119 | 120 | isOperatorAdjacentToNonspace: function() { 121 | if (this.hasOperatorThenNonspaces() && !this.isException(this.prevToken)) { 122 | return true; 123 | } 124 | if (this.hasNonspacesThenOperator() && !this.isException(this.token)) { 125 | return true; 126 | } 127 | return false; 128 | }, 129 | 130 | isException: function(token) { 131 | return this.isUnary(token) || this.isNonConditionalColonOperator(token); 132 | }, 133 | 134 | hasOperatorThenNonspaces: function() { 135 | return this.isOperator(this.prevToken) && !this.isWhitespaces(this.token); 136 | }, 137 | 138 | hasNonspacesThenOperator: function() { 139 | return!this.isOnlySpaces(this.prevToken) && this.isOperator(this.token); 140 | }, 141 | 142 | onFalseTransformData: function(token) { 143 | this.token = token; 144 | var prevToken = this.prevToken; 145 | if (this.hasOperatorThenSpaces()) { 146 | token.value = ''; 147 | } else if (this.hasSpacesThenOperator()) { 148 | prevToken.value = ''; 149 | } 150 | prevToken && this.output.write(prevToken); 151 | this.prevToken = token; 152 | }, 153 | 154 | hasOperatorThenSpaces: function() { 155 | return this.isOperator(this.prevToken) && this.isOnlySpaces(this.token); 156 | }, 157 | 158 | hasSpacesThenOperator: function() { 159 | return this.isOperator(this.token) && this.isOnlySpaces(this.prevToken); 160 | }, 161 | 162 | onHybridTransformData: function(token) { 163 | if (this.isNonConditionalColonOperator(token)) { 164 | this.onFalseTransformData(token); 165 | return; 166 | } 167 | 168 | this.token = token; 169 | var prevToken = this.prevToken; 170 | 171 | if (prevToken) { 172 | if (this.shouldHybridRemoveTokenSpace()) { 173 | this.token.value = ''; 174 | } else if (this.shouldHybridRemovePrevTokenSpace()) { 175 | prevToken.value = ''; 176 | } 177 | this.output.write(prevToken); 178 | if (this.shouldHybridAddSpace()) { 179 | this.output.write(this.tokens.space); 180 | } 181 | } 182 | this.prevToken = token; 183 | }, 184 | 185 | shouldRemoveTokenSpace: function() { 186 | return this.hasOperatorThenSpaces() && this.isUnary(this.prevToken); 187 | }, 188 | 189 | shouldHybridRemoveTokenSpace: function() { 190 | return this.hasOperatorThenSpaces() && this.isHybridGroupToken(this.prevToken); 191 | }, 192 | 193 | shouldHybridRemovePrevTokenSpace: function() { 194 | return this.hasSpacesThenOperator() && this.isHybridGroupToken(this.token); 195 | }, 196 | 197 | isHybridGroupToken: function(token) { 198 | return this.isHybridGroupOperator(token) || this.isUnary(token); 199 | }, 200 | 201 | shouldHybridAddSpace: function() { 202 | if (this.hasOperatorThenNonspaces() && !this.isHybridGroupToken(this.prevToken)) { 203 | return true; 204 | } 205 | if (this.hasNonspacesThenOperator() && !this.isHybridGroupToken(this.token)) { 206 | return true; 207 | } 208 | return false; 209 | }, 210 | 211 | onTransformEnd: function() { 212 | this.token && this.output.write(this.token); 213 | this.output.end(); 214 | }, 215 | 216 | isOperator: function(token) { 217 | return this.isPunctuator(token) && ~this.operators.indexOf(token.value); 218 | }, 219 | 220 | isNonConditionalColonOperator: function(token) { 221 | return this.isPunctuator(token) && token.value === ':' && 222 | token.grammarToken.type !== 'ConditionalExpression'; 223 | }, 224 | 225 | isUnary: function(token) { 226 | return this.isOperator(token) && token.grammarToken.type === 'UnaryExpression'; 227 | }, 228 | 229 | isUnaryThenSpace: function() { 230 | return this.isUnary(this.prevToken) && this.isWhitespaces(this.token); 231 | }, 232 | 233 | isHybridGroupOperator: function(token) { 234 | return this.isPunctuator(token) && ~this.hybridGroupOperators.indexOf(token.value); 235 | } 236 | 237 | }); 238 | -------------------------------------------------------------------------------- /lib/rules/spaces_in_brackets.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Rule = require('../Rule'); 4 | var util = require('../util'); 5 | 6 | 7 | function SpacesInBracketsRule() { 8 | SpacesInBracketsRule.super_.apply(this, arguments); 9 | } 10 | 11 | module.exports = util.inherits(SpacesInBracketsRule, Rule, { 12 | 13 | name: 'spaces_in_brackets', 14 | 15 | supportedSettings: { 16 | spaces_in_brackets: [true, false, 'hybrid'] 17 | }, 18 | 19 | brackets: ['(', '[', '{', ')', ']', '}'], 20 | 21 | openBrackets: ['(', '[', '{'], 22 | 23 | closeBrackets: [')', ']', '}'], 24 | 25 | infer: function(sample, callback) { 26 | this.prevTokens = new Array(2); 27 | this.trueTrend = 0; 28 | this.falseTrend = 0; 29 | this.callback = callback; 30 | 31 | sample.on('data', this.onInferData.bind(this)); 32 | sample.on('end', this.onInferEnd.bind(this)); 33 | }, 34 | 35 | onInferData: function(token) { 36 | 37 | this.setFriendlyTokenNames(token); 38 | 39 | if (this.isTrueStyle()) { 40 | this.trueTrend++; 41 | } else if (this.isFalseStyle()) { 42 | this.falseTrend++; 43 | } 44 | 45 | this.shiftTokens(token); 46 | }, 47 | 48 | setFriendlyTokenNames: function(token) { 49 | this.token = token; 50 | this.prevToken = this.prevTokens[1]; 51 | this.prevPrevToken = this.prevTokens[0]; 52 | }, 53 | 54 | isTrueStyle: function() { 55 | return this.hasSpaceInsideOpenBracket() || this.hasSpaceInsideCloseBracket(); 56 | }, 57 | 58 | isFalseStyle: function() { 59 | return this.hasNonSpaceInsideBracket(); 60 | }, 61 | 62 | shiftTokens: function() { 63 | this.prevTokens.shift(); 64 | this.prevTokens.push(this.token); 65 | }, 66 | 67 | onInferEnd: function() { 68 | var setting; 69 | 70 | if (this.trueTrend > this.falseTrend) { 71 | setting = (this.falseTrend === 0) ? true : 'hybrid'; 72 | } else if (this.falseTrend > this.trueTrend) { 73 | setting = (this.trueTrend === 0) ? false : 'hybrid'; 74 | } else { 75 | setting = null; 76 | } 77 | 78 | this.callback({spaces_in_brackets: setting}); 79 | }, 80 | 81 | transform: function(input) { 82 | Rule.prototype.transform.apply(this, arguments); 83 | if (!this.enforceRule) { 84 | return; 85 | } 86 | 87 | this.prevTokens = new Array(2); 88 | 89 | switch (this.settings['spaces_in_brackets']) { 90 | case true: 91 | input.on('data', this.onTrueTransformData.bind(this)); 92 | input.on('end', this.onTransformEnd.bind(this)); 93 | break; 94 | case false: 95 | input.on('data', this.onFalseTransformData.bind(this)); 96 | input.on('end', this.onTransformEnd.bind(this)); 97 | break; 98 | case 'hybrid': 99 | input.on('data', this.onHybridTransformData.bind(this)); 100 | input.on('end', this.onTransformEnd.bind(this)); 101 | break; 102 | } 103 | }, 104 | 105 | onTrueTransformData: function(token) { 106 | this.setFriendlyTokenNames(token); 107 | 108 | if (this.hasNonSpaceInsideBracket()) { 109 | this.output.write(this.tokens.space); 110 | } 111 | 112 | this.onAfterEachTransformData(); 113 | }, 114 | 115 | hasNonSpaceInsideBracket: function() { 116 | if (this.isOpenBracket(this.prevToken) && !this.isWhitespaces(this.token)) { 117 | return true; 118 | } 119 | if (!this.isWhitespaces(this.prevToken) && this.isCloseBracket(this.token)) { 120 | return true; 121 | } 122 | return false; 123 | }, 124 | 125 | hasCloseParenThenOpenCurlyBrace: function() { 126 | return this.isCloseParen(this.prevToken) && this.isOpenCurlyBrace(this.token); 127 | }, 128 | 129 | onAfterEachTransformData: function() { 130 | if (this.hasCloseParenThenOpenCurlyBrace()) { 131 | this.output.write(this.tokens.space); 132 | } 133 | if (this.isWhitespaces(this.prevToken)) { 134 | this.output.write(this.prevToken); 135 | } 136 | if (!this.isWhitespaces(this.token)) { 137 | this.output.write(this.token); 138 | } 139 | this.shiftTokens(); 140 | }, 141 | 142 | onFalseTransformData: function(token) { 143 | this.setFriendlyTokenNames(token); 144 | 145 | if (this.hasSpaceInsideOpenBracket()) { 146 | token.value = ''; 147 | } else if (this.hasSpaceInsideCloseBracket()) { 148 | this.prevToken.value = ''; 149 | } 150 | 151 | this.onAfterEachTransformData(); 152 | }, 153 | 154 | hasSpaceInsideOpenBracket: function() { 155 | return this.isOpenBracket(this.prevToken) && this.isWhitespacesSansNewline(this.token); 156 | }, 157 | 158 | hasSpaceInsideCloseBracket: function() { 159 | return this.isWhitespacesSansNewline(this.prevToken) && this.isCloseBracket(this.token); 160 | }, 161 | 162 | onHybridTransformData: function(token) { 163 | this.setFriendlyTokenNames(token); 164 | 165 | if (this.shouldHybridRemoveSpace()) { 166 | this.prevToken.value = ''; 167 | } 168 | 169 | if (this.shouldHybridAddSpace()) { 170 | this.output.write(this.tokens.space); 171 | } 172 | 173 | this.onAfterEachTransformData(); 174 | }, 175 | 176 | shouldHybridRemoveSpace: function() { 177 | return this.hasBracketSpaceBracket() || this.hasBracketSpaceFunction(); 178 | }, 179 | 180 | hasBracketSpaceBracket: function() { 181 | if (this.isWhitespacesSansNewline(this.prevToken)) { 182 | if (this.isCloseBracket(this.prevPrevToken) && this.isOpenBracket(this.token)) { 183 | return false; 184 | } 185 | if (this.isBracket(this.prevPrevToken) && this.isBracket(this.token)) { 186 | return true; 187 | } 188 | } 189 | return false; 190 | }, 191 | 192 | hasBracketSpaceFunction: function() { 193 | return this.isOpenBracket(this.prevPrevToken) && 194 | this.isWhitespacesSansNewline(this.prevToken) && 195 | this.isFunctionKeyword(this.token); 196 | }, 197 | 198 | shouldHybridAddSpace: function() { 199 | if (this.isOpenBracket(this.prevToken) && !this.isWhitespaces(this.token) && 200 | !this.isBracket(this.token) && !this.isFunctionKeyword(this.token)) { 201 | return true; 202 | } 203 | if (this.isCloseBracket(this.token) && 204 | !this.isWhitespaces(this.prevToken) && !this.isBracket(this.prevToken)) { 205 | return true; 206 | } 207 | return false; 208 | }, 209 | 210 | onTransformEnd: function() { 211 | this.token && this.output.write(this.token); 212 | this.output.end(); 213 | }, 214 | 215 | isBracket: function(token) { 216 | return this.isPunctuator(token) && this.brackets.indexOf(token.value) !== -1; 217 | }, 218 | 219 | isOpenBracket: function(token) { 220 | return this.isPunctuator(token) && this.openBrackets.indexOf(token.value) !== -1; 221 | }, 222 | 223 | isCloseBracket: function(token) { 224 | return this.isPunctuator(token) && this.closeBrackets.indexOf(token.value) !== -1; 225 | } 226 | 227 | }); 228 | -------------------------------------------------------------------------------- /lib/rules/trim_trailing_whitespace.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var assert = require('assert'); 3 | 4 | var string = require('../util/string'); 5 | var Rule = require('../Rule'); 6 | var util = require('../util'); 7 | 8 | 9 | function TrimTrailingWhitespaceRule() { 10 | TrimTrailingWhitespaceRule.super_.apply(this, arguments); 11 | } 12 | 13 | module.exports = util.inherits(TrimTrailingWhitespaceRule, Rule, { 14 | 15 | name: 'trim_trailing_whitespace', 16 | 17 | supportedSettings: { 18 | trim_trailing_whitespace: [true, false] 19 | }, 20 | 21 | infer: function(sample, callback) { 22 | 23 | var hasTrailingWhitespace = false; 24 | 25 | sample.on('data', function(token) { 26 | if (this.isWhitespaces(token) && /[\t ]\r?\n/.test(token.value)) { 27 | hasTrailingWhitespace = true; 28 | } 29 | }.bind(this)); 30 | 31 | sample.on('end', function() { 32 | callback({trim_trailing_whitespace:!hasTrailingWhitespace}); 33 | }); 34 | }, 35 | 36 | transform: function() { 37 | Rule.prototype.transform.apply(this, arguments); 38 | if (!this.enforceRule) { 39 | return; 40 | } 41 | 42 | if (!this.settings['trim_trailing_whitespace']) { 43 | this.skipRule(); 44 | return; 45 | } 46 | 47 | this.bindEvents(); 48 | }, 49 | 50 | bindEvents: function() { 51 | this.input.on('data', this.onTransformData.bind(this)); 52 | this.input.on('end', this.onTransformEnd.bind(this)); 53 | }, 54 | 55 | onTransformData: function(token) { 56 | if (this.isWhitespaces(token)) { 57 | token.value = token.value.replace(/[\t ]+(?=\r?\n)/g, ''); 58 | } 59 | this.output.write(token); 60 | } 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /lib/styles/codepainter.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "tab", 3 | "trim_trailing_whitespace": true, 4 | "end_of_line": "lf", 5 | "insert_final_newline": true, 6 | "quote_type": "auto", 7 | "spaces_around_operators": true, 8 | "space_after_control_statements": true, 9 | "space_after_anonymous_functions": false, 10 | "spaces_in_brackets": false 11 | } 12 | -------------------------------------------------------------------------------- /lib/styles/hautelook.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "space", 3 | "indent_size": 4, 4 | "trim_trailing_whitespace": true, 5 | "end_of_line": "lf", 6 | "insert_final_newline": true, 7 | "quote_type": "auto", 8 | "spaces_around_operators": true, 9 | "space_after_control_statements": true, 10 | "space_after_anonymous_functions": false, 11 | "spaces_in_brackets": false 12 | } 13 | -------------------------------------------------------------------------------- /lib/styles/idiomatic.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "space", 3 | "indent_size": 2, 4 | "trim_trailing_whitespace": true, 5 | "spaces_around_operators": true, 6 | "space_after_control_statements": true, 7 | "space_after_anonymous_functions": true, 8 | "spaces_in_brackets": "hybrid" 9 | } 10 | -------------------------------------------------------------------------------- /lib/styles/mediawiki.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "tab", 3 | "trim_trailing_whitespace": true, 4 | "insert_final_newline": true, 5 | "spaces_around_operators": true, 6 | "space_after_control_statements": true, 7 | "space_after_anonymous_functions": true, 8 | "spaces_in_brackets": true 9 | } 10 | -------------------------------------------------------------------------------- /lib/util/index.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var glob = require('glob'); 3 | var util = require('util'); 4 | 5 | 6 | module.exports = { 7 | 8 | inherits: function(sub, sup, proto) { 9 | util.inherits(sub, sup); 10 | if (typeof proto !== 'undefined') { 11 | Object.keys(proto).forEach(function(key) { 12 | sub.prototype[key] = proto[key]; 13 | }); 14 | } 15 | return sub; 16 | }, 17 | 18 | reduceGlobs: function(globs, callback) { 19 | var result = []; 20 | async.map(globs, onEachGlob, function(err, fileListList) { 21 | fileListList.forEach(function(fileList) { 22 | result.push.apply(result, fileList.filter(filterOutExisting)); 23 | }); 24 | callback(result); 25 | }); 26 | function onEachGlob(globPattern, cb) { 27 | glob(globPattern, function(err, paths) { 28 | cb(null, paths); 29 | }); 30 | } 31 | function filterOutExisting(file) { 32 | return result.indexOf(file) === -1; 33 | } 34 | } 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /lib/util/string.js: -------------------------------------------------------------------------------- 1 | if (!String.prototype.repeat) { 2 | String.prototype.repeat = function(count) { 3 | return count > 0 ? new Array(count + 1).join(this) : ''; 4 | }; 5 | } 6 | 7 | if (!String.prototype.reverse) { 8 | String.prototype.reverse = function() { 9 | return this.split('').reverse().join(''); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codepainter", 3 | "version": "0.4.5", 4 | "author": "Jakub Wieczorek ", 5 | "description": "A JavaScript beautifier that can both infer coding style and transform code to reflect that style. You can also set style preferences explicitly in a variety of ways.", 6 | "contributors": [ 7 | "Jed Mao ", 8 | "Jakub Wieczorek (http://jakubw.net)" 9 | ], 10 | "main": "./codepainter", 11 | "bin": { 12 | "codepaint": "./bin/codepaint", 13 | "codepainter": "./bin/codepaint" 14 | }, 15 | "scripts": { 16 | "test": "mocha -R spec", 17 | "codepaint": "codepaint xform -e **/**.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "http://github.com/jedmao/codepainter.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/jedmao/codepainter/issues" 25 | }, 26 | "keywords": [ 27 | "code", 28 | "formatting", 29 | "style", 30 | "editorconfig" 31 | ], 32 | "dependencies": { 33 | "async": "^2.0.0-rc.5", 34 | "cli-color": "^1.1.0", 35 | "editorconfig": "^0.13.2", 36 | "esprima": "^2.7.2", 37 | "gitlike-cli": "^0.1.0", 38 | "glob": "^7.0.3", 39 | "memorystream": "^0.3.1", 40 | "mkdirp": "^0.5.1", 41 | "node.extend": "^1.1.5" 42 | }, 43 | "devDependencies": { 44 | "mocha": "^2.5.3", 45 | "should": "^8.4.0" 46 | }, 47 | "engine": "node >= 0.10.6", 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /test/cases.js: -------------------------------------------------------------------------------- 1 | var editorconfig = require('editorconfig'); 2 | var fs = require('fs'); 3 | var glob = require('glob'); 4 | var MemoryStream = require('memorystream'); 5 | var path = require('path'); 6 | var should = require('should'); 7 | 8 | var Pipe = require('../lib/Pipe'); 9 | var codepaint = require('../codepainter'); 10 | var rules = require('../lib/rules'); 11 | 12 | 13 | describe('Code Painter', function() { 14 | 15 | glob.sync('test/cases/*').forEach(function(testCase) { 16 | 17 | testCase = testCase.substr(testCase.lastIndexOf('/') + 1); 18 | 19 | describe(testCase + ' rule', function() { 20 | var Rule; 21 | for (var i = 0; i < rules.length; i++) { 22 | if (rules[i].prototype.name === testCase) { 23 | Rule = rules[i]; 24 | break; 25 | } 26 | } 27 | 28 | glob.sync('test/cases/' + testCase + '/*/*.json').forEach(function(stylePath) { 29 | var setting = { 30 | folder: stylePath.substr(0, stylePath.lastIndexOf('/') + 1), 31 | styles: JSON.parse(fs.readFileSync(stylePath, 'utf-8')) 32 | }; 33 | 34 | if (editorconfig.parse(stylePath).test !== true) { 35 | return; 36 | } 37 | 38 | testInferrance(Rule, setting); 39 | testTransformation(setting); 40 | }); 41 | }); 42 | }); 43 | }); 44 | 45 | function testInferrance(Rule, setting) { 46 | Object.keys(setting.styles).forEach(function(styleKey) { 47 | var styleValue = setting.styles[styleKey]; 48 | var samplePath = verifyPath(setting.folder + 'sample.js'); 49 | if (fs.existsSync(samplePath)) { 50 | it('infers ' + styleKey + ' setting as ' + styleValue, function(done) { 51 | codepaint.infer(samplePath, function(inferredStyle) { 52 | styleValue.should.equal(inferredStyle[styleKey]); 53 | done(); 54 | }, Rule); 55 | }); 56 | } 57 | }); 58 | } 59 | 60 | function verifyPath(path) { 61 | fs.existsSync(path).should.be.true; 62 | return path; 63 | } 64 | 65 | function testTransformation(setting) { 66 | var folders = setting.folder.split('/'); 67 | setting.name = folders[folders.length - 2]; 68 | it('formats ' + setting.name + ' setting properly', function(done) { 69 | var inputPath = setting.folder + 'input.js'; 70 | var expectedPath = verifyPath(setting.folder + 'expected.js'); 71 | 72 | var outputStream = new MemoryStream(); 73 | var output = ''; 74 | outputStream.on('data', function(chunk) { 75 | output += chunk; 76 | }); 77 | 78 | var options = { 79 | style: setting.styles, 80 | output: outputStream 81 | }; 82 | 83 | codepaint.xform(inputPath, options, function() { 84 | var expected = fs.readFileSync(expectedPath, 'utf8'); 85 | output.should.equal(expected); 86 | done(); 87 | }); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /test/cases/.editorconfig: -------------------------------------------------------------------------------- 1 | ; We don't want EditorConfig settings to mess with test cases that are purposely formatted the way they are 2 | root = true 3 | 4 | [**/**] 5 | test = true 6 | 7 | [*.json] 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /test/cases/end_of_line/.gitattributes: -------------------------------------------------------------------------------- 1 | # Turn off LF normalization for eol test cases 2 | *.js -text 3 | -------------------------------------------------------------------------------- /test/cases/end_of_line/crlf/expected.js: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | var c = 3; 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/crlf/input.js: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | var c = 3; 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/crlf/sample.js: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | var c = 3; 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/crlf/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "end_of_line": "crlf" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/lf/expected.js: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | var c = 3; 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/lf/input.js: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | var c = 3; 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/lf/sample.js: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | var c = 3; 4 | -------------------------------------------------------------------------------- /test/cases/end_of_line/lf/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "end_of_line": "lf" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_2/expected.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_2/input.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_2/sample.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_2/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "space", 3 | "indent_size": 2 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_4/expected.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_4/input.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_4/sample.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/space_4/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "space", 3 | "indent_size": 4 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/tab/expected.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | if (true) 23 | var x = 5; 24 | while (true) 25 | break; 26 | for (var i = 0; i < 10; i++) 27 | break; 28 | do { 29 | break; 30 | } while (true); 31 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/tab/input.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | if (true) 23 | var x = 5; 24 | while (true) 25 | break; 26 | for (var i = 0; i < 10; i++) 27 | break; 28 | do { 29 | break; 30 | } while (true); 31 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/tab/sample.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | // A useless comment 9 | continue; 10 | } 11 | do{ 12 | break; 13 | } while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | if (true) 23 | var x = 5; 24 | while (true) 25 | break; 26 | for (var i = 0; i < 10; i++) 27 | break; 28 | do { 29 | break; 30 | } while (true); 31 | -------------------------------------------------------------------------------- /test/cases/indent_style_and_size/tab/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "indent_style": "tab" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/insert_final_newline/false/expected.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } -------------------------------------------------------------------------------- /test/cases/insert_final_newline/false/input.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/cases/insert_final_newline/false/sample.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } -------------------------------------------------------------------------------- /test/cases/insert_final_newline/false/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "insert_final_newline": false 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/insert_final_newline/true/expected.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/insert_final_newline/true/input.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } -------------------------------------------------------------------------------- /test/cases/insert_final_newline/true/sample.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/insert_final_newline/true/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "insert_final_newline": true 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/quote_type/auto/expected.js: -------------------------------------------------------------------------------- 1 | var a = 'foo bar baz', 2 | b = "foo 'bar' baz", 3 | c = "foo \'bar\' baz", 4 | d = 'foo "bar" baz', 5 | e = 'foo \\\\"bar\\" baz', 6 | f = 'foo bar baz', 7 | g = 'foo "bar" baz', 8 | h = "foo \\'\\' bar \\ baz"; 9 | -------------------------------------------------------------------------------- /test/cases/quote_type/auto/input.js: -------------------------------------------------------------------------------- 1 | var a = "foo bar baz", 2 | b = "foo 'bar' baz", 3 | c = "foo \'bar\' baz", 4 | d = "foo \"bar\" baz", 5 | e = "foo \\\\\"bar\\\" baz", 6 | f = 'foo bar baz', 7 | g = 'foo "bar" baz', 8 | h = 'foo \\\'\\\' bar \\ baz'; 9 | -------------------------------------------------------------------------------- /test/cases/quote_type/auto/sample.js: -------------------------------------------------------------------------------- 1 | var a = 'foo "bar" baz', 2 | b = "'foo' 'bar' 'baz'"; 3 | -------------------------------------------------------------------------------- /test/cases/quote_type/auto/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "quote_type": "auto" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/quote_type/double/expected.js: -------------------------------------------------------------------------------- 1 | var a = "foo bar baz", 2 | b = "foo 'bar' baz", 3 | c = "foo \'bar\' baz", 4 | d = "foo \"bar\" baz", 5 | e = "foo \\\\\"bar\\\" baz", 6 | f = "foo bar baz", 7 | g = "foo \"bar\" baz", 8 | h = "foo \\'\\' bar \\ baz"; 9 | -------------------------------------------------------------------------------- /test/cases/quote_type/double/input.js: -------------------------------------------------------------------------------- 1 | var a = "foo bar baz", 2 | b = "foo 'bar' baz", 3 | c = "foo \'bar\' baz", 4 | d = "foo \"bar\" baz", 5 | e = "foo \\\\\"bar\\\" baz", 6 | f = 'foo bar baz', 7 | g = 'foo "bar" baz', 8 | h = 'foo \\\'\\\' bar \\ baz'; 9 | -------------------------------------------------------------------------------- /test/cases/quote_type/double/sample.js: -------------------------------------------------------------------------------- 1 | var a = "'foo' 'bar' 'baz'"; 2 | -------------------------------------------------------------------------------- /test/cases/quote_type/double/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "quote_type": "double" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/quote_type/single/expected.js: -------------------------------------------------------------------------------- 1 | var a = 'foo bar baz', 2 | b = 'foo \'bar\' baz', 3 | c = 'foo \'bar\' baz', 4 | d = 'foo "bar" baz', 5 | e = 'foo \\\\"bar\\" baz', 6 | f = 'foo bar baz', 7 | g = 'foo \"bar\" baz', 8 | h = 'foo \\\'\\\' bar \\ baz'; 9 | -------------------------------------------------------------------------------- /test/cases/quote_type/single/input.js: -------------------------------------------------------------------------------- 1 | var a = "foo bar baz", 2 | b = "foo 'bar' baz", 3 | c = "foo \'bar\' baz", 4 | d = "foo \"bar\" baz", 5 | e = "foo \\\\\"bar\\\" baz", 6 | f = 'foo bar baz', 7 | g = 'foo \"bar\" baz', 8 | h = 'foo \\\'\\\' bar \\ baz'; 9 | -------------------------------------------------------------------------------- /test/cases/quote_type/single/sample.js: -------------------------------------------------------------------------------- 1 | var a = '"foo" "bar" "baz"'; 2 | -------------------------------------------------------------------------------- /test/cases/quote_type/single/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "quote_type": "single" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/false/expected.js: -------------------------------------------------------------------------------- 1 | var x = function(){}; 2 | var y = function(){}; 3 | var z = function() { 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/false/input.js: -------------------------------------------------------------------------------- 1 | var x = function (){}; 2 | var y = function(){}; 3 | var z = function () { 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/false/sample.js: -------------------------------------------------------------------------------- 1 | var x = function(){}; 2 | var y = function (){}; 3 | var z = function() { 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/false/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "space_after_anonymous_functions": false 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/true/expected.js: -------------------------------------------------------------------------------- 1 | var x = function (){}; 2 | var y = function (){}; 3 | var z = function () { 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/true/input.js: -------------------------------------------------------------------------------- 1 | var x = function(){}; 2 | var y = function (){}; 3 | var z = function() { 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/true/sample.js: -------------------------------------------------------------------------------- 1 | var x = function (){}; 2 | var y = function(){}; 3 | var z = function () { 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/space_after_anonymous_functions/true/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "space_after_anonymous_functions": true 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/false/expected.js: -------------------------------------------------------------------------------- 1 | if(true) { 2 | while(true) { 3 | break; 4 | } 5 | } 6 | else { 7 | for(var i = 0; i < 10; i++) { 8 | continue; 9 | } 10 | do { 11 | break; 12 | } 13 | while(false); 14 | } 15 | switch(true) { 16 | case true: 17 | with(document) { 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/false/input.js: -------------------------------------------------------------------------------- 1 | if (true) { 2 | while (true) { 3 | break; 4 | } 5 | } 6 | else { 7 | for (var i = 0; i < 10; i++) { 8 | continue; 9 | } 10 | do { 11 | break; 12 | } 13 | while (false); 14 | } 15 | switch (true) { 16 | case true: 17 | with (document) { 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/false/sample.js: -------------------------------------------------------------------------------- 1 | if( true ){ 2 | while( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | continue; 9 | } 10 | do{ 11 | break; 12 | } 13 | while( false ); 14 | } 15 | switch( true ){ 16 | case true: 17 | with( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/false/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "space_after_control_statements": false 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/true/expected.js: -------------------------------------------------------------------------------- 1 | if ( true ){ 2 | while ( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for ( var i = 0; i < 10; i++ ){ 8 | continue; 9 | } 10 | do{ 11 | break; 12 | } 13 | while ( false ); 14 | } 15 | switch ( true ){ 16 | case true: 17 | with ( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/true/input.js: -------------------------------------------------------------------------------- 1 | if( true ){ 2 | while( true ){ 3 | break; 4 | } 5 | } 6 | else{ 7 | for( var i = 0; i < 10; i++ ){ 8 | continue; 9 | } 10 | do{ 11 | break; 12 | } 13 | while( false ); 14 | } 15 | switch( true ){ 16 | case true: 17 | with( document ){ 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/true/sample.js: -------------------------------------------------------------------------------- 1 | if (true) { 2 | while (true) { 3 | break; 4 | } 5 | } 6 | else { 7 | for(var i = 0; i < 10; i++) { 8 | continue; 9 | } 10 | do { 11 | break; 12 | } 13 | while (false); 14 | } 15 | switch (true) { 16 | case true: 17 | with (document) { 18 | write('foo'); 19 | } 20 | break; 21 | } 22 | -------------------------------------------------------------------------------- /test/cases/space_after_control_statements/true/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "space_after_control_statements": true 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/false/expected.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a=x+++--y, 3 | b=!x&&~y||x*y&x/y|x%y|+x|-y, 4 | c=x+y||x-y, 5 | d=x<>y|x>>>y, 6 | e=xy||x>=y, 7 | f=x==y!=x===y!==y, 8 | g=x&y^x|y&&x||y?x:y; 9 | 10 | x=y; 11 | x+=y; 12 | x-=y; 13 | x*=y; 14 | x/=y; 15 | x%=y; 16 | x<<=y; 17 | x>>=y; 18 | x>>>=y; 19 | x&=y; 20 | x^=y; 21 | x|=y; 22 | x=!!y; 23 | x=~y; 24 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/false/input.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a = x++ + --y, 3 | b = ! x && ~ y || x * y & x / y | x % y | +x | -y, 4 | c = x + y || x - y, 5 | d = x << y & x >> y | x >>> y, 6 | e = x < y || x <= y && x > y || x >= y, 7 | f = x == y != x === y !== y, 8 | g = x & y ^ x | y && x || y ? x : y; 9 | 10 | x = y; 11 | x += y; 12 | x -= y; 13 | x *= y; 14 | x /= y; 15 | x %= y; 16 | x <<= y; 17 | x >>= y; 18 | x >>>= y; 19 | x &= y; 20 | x ^= y; 21 | x |= y; 22 | x = ! ! y; 23 | x = ~ y; 24 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/false/sample.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a=x+++--y, 3 | b=!x&&~y||x*y&x/y|x%y|+x|-y, 4 | c=x+y||x-y, 5 | d=x<>y|x>>>y, 6 | e=xy||x>=y, 7 | f=x==y!=x===y!==y, 8 | g=x&y^x|y&&x||y?x:y; 9 | 10 | x=y; 11 | x+=y; 12 | x-=y; 13 | x*=y; 14 | x/=y; 15 | x%=y; 16 | x<<=y; 17 | x>>=y; 18 | x>>>=y; 19 | x&=y; 20 | x^=y; 21 | x|=y; 22 | x=!!y; 23 | x=~y; 24 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/false/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "spaces_around_operators": false 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/hybrid/expected.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a = x++ + --y, 3 | b = !x && ~y || x*y & x/y | x%y | +x | -y, 4 | c = x + y || x - y, 5 | d = x << y & x >> y | x >>> y, 6 | e = x < y || x <= y && x > y || x >= y, 7 | f = x == y != x === y !== y, 8 | g = x & y ^ x | y && x || y ? x : y; 9 | 10 | x = y; 11 | x += y; 12 | x -= y; 13 | x *= y; 14 | x /= y; 15 | x %= y; 16 | x <<= y; 17 | x >>= y; 18 | x >>>= y; 19 | x &= y; 20 | x ^= y; 21 | x |= y; 22 | x = !!y; 23 | x = ~y; 24 | 25 | switch(true) { 26 | case 'foo': 27 | break; 28 | case true: 29 | break; 30 | default: 31 | break; 32 | } 33 | 34 | var x = { 35 | foo: 'bar', 36 | 'baz': 'qux' 37 | }; 38 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/hybrid/input.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a=x+++--y, 3 | b=!x&&~y||x*y&x/y|x%y|+x|-y, 4 | c=x+y||x-y, 5 | d=x<>y|x>>>y, 6 | e=xy||x>=y, 7 | f=x==y!=x===y!==y, 8 | g=x&y^x|y&&x||y?x:y; 9 | 10 | x=y; 11 | x+=y; 12 | x-=y; 13 | x*=y; 14 | x/=y; 15 | x%=y; 16 | x<<=y; 17 | x>>=y; 18 | x>>>=y; 19 | x&=y; 20 | x^=y; 21 | x|=y; 22 | x=!! y; 23 | x=~ y; 24 | 25 | switch(true) { 26 | case 'foo' : 27 | break; 28 | case true : 29 | break; 30 | default : 31 | break; 32 | } 33 | 34 | var x = { 35 | foo : 'bar', 36 | 'baz' : 'qux' 37 | }; 38 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/hybrid/sample.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a = x++ + --y, 3 | b = !x && ~y || x*y & x/y | x%y | +x | -y, 4 | c = x + y || x - y, 5 | d = x << y & x >> y | x >>> y, 6 | e = x < y || x <= y && x > y || x >= y, 7 | f = x == y != x === y !== y, 8 | g = x & y ^ x | y && x || y ? x : y; 9 | 10 | x = y; 11 | x += y; 12 | x -= y; 13 | x *= y; 14 | x /= y; 15 | x %= y; 16 | x <<= y; 17 | x >>= y; 18 | x >>>= y; 19 | x &= y; 20 | x ^= y; 21 | x |= y; 22 | x = !!y; 23 | x = ~y; 24 | 25 | switch(true) { 26 | case 'foo': 27 | break; 28 | case true: 29 | break; 30 | default: 31 | break; 32 | } 33 | 34 | var x = { 35 | foo: 'bar', 36 | 'baz': 'qux' 37 | }; 38 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/hybrid/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "spaces_around_operators": "hybrid" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/true/expected.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a = x++ + --y, 3 | b = !x && ~y || x * y & x / y | x % y | +x | -y, 4 | c = x + y || x - y, 5 | d = x << y & x >> y | x >>> y, 6 | e = x < y || x <= y && x > y || x >= y, 7 | f = x == y != x === y !== y, 8 | g = x & y ^ x | y && x || y ? x : y; 9 | 10 | x = y; 11 | x += y; 12 | x -= y; 13 | x *= y; 14 | x /= y; 15 | x %= y; 16 | x <<= y; 17 | x >>= y; 18 | x >>>= y; 19 | x &= y; 20 | x ^= y; 21 | x |= y; 22 | 23 | switch(true) { 24 | case 'foo': 25 | break; 26 | case true: 27 | break; 28 | default: 29 | break; 30 | } 31 | 32 | var x = { 33 | foo: 'bar', 34 | 'baz': 'qux' 35 | }; 36 | 37 | var x = (true) ? 4 : 2; 38 | 39 | var x = !!y; 40 | var x = ~y; 41 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/true/input.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a=x+++--y, 3 | b=!x&&~y||x*y&x/y|x%y|+x|-y, 4 | c=x+y||x-y, 5 | d=x<>y|x>>>y, 6 | e=xy||x>=y, 7 | f=x==y!=x===y!==y, 8 | g=x&y^x|y&&x||y?x:y; 9 | 10 | x=y; 11 | x+=y; 12 | x-=y; 13 | x*=y; 14 | x/=y; 15 | x%=y; 16 | x<<=y; 17 | x>>=y; 18 | x>>>=y; 19 | x&=y; 20 | x^=y; 21 | x|=y; 22 | 23 | switch(true) { 24 | case 'foo' : 25 | break; 26 | case true : 27 | break; 28 | default : 29 | break; 30 | } 31 | 32 | var x = { 33 | foo : 'bar', 34 | 'baz' : 'qux' 35 | }; 36 | 37 | var x=(true)?4:2; 38 | 39 | var x=!! y; 40 | var x= ~ y; 41 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/true/sample.js: -------------------------------------------------------------------------------- 1 | var x, y, 2 | a = x++ + --y, 3 | b = ! x && ~ y || x * y & x / y | x % y | + x | - y, 4 | c = x + y || x - y, 5 | d = x << y & x >> y | x >>> y, 6 | e = x < y || x <= y && x > y || x >= y, 7 | f = x == y != x === y !== y, 8 | g = x & y ^ x | y && x || y ? x : y; 9 | 10 | x = y; 11 | x += y; 12 | x -= y; 13 | x *= y; 14 | x /= y; 15 | x %= y; 16 | x <<= y; 17 | x >>= y; 18 | x >>>= y; 19 | x &= y; 20 | x ^= y; 21 | x |= y; 22 | 23 | switch(true) { 24 | case 'foo': 25 | break; 26 | case true: 27 | break; 28 | default: 29 | break; 30 | } 31 | 32 | var x = { 33 | foo: 'bar', 34 | 'baz': 'qux' 35 | }; 36 | 37 | var x = (true) ? 4 : 2; 38 | 39 | var x = !!y; 40 | var x = ~y; 41 | -------------------------------------------------------------------------------- /test/cases/spaces_around_operators/true/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "spaces_around_operators": true 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/false/expected.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if (condition) { 3 | // statements 4 | } 5 | 6 | while (condition) { 7 | // statements 8 | } 9 | 10 | for (var i = 0; i < 100; i++) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for (prop in object) { 17 | // statements 18 | } 19 | 20 | if (true) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo(arg1, argN) { 29 | 30 | } 31 | 32 | // Usage 33 | foo(arg1, argN); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square(number) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square(10); 43 | 44 | // Really contrived continuation passing style 45 | function square(number, callback) { 46 | callback(number * number); 47 | } 48 | 49 | square(10, function(square) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function(number) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial(number) { 64 | if (number < 2) { 65 | return 1; 66 | } 67 | 68 | return number * factorial(number - 1); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar(options) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar({a: "alpha"}); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo(function() { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | }); 90 | 91 | // Function accepting an array, no space 92 | foo(["alpha", "beta"]); 93 | 94 | // 2.C.1.2 95 | // Function accepting an object, no space 96 | foo({ 97 | a: "alpha", 98 | b: "beta" 99 | }); 100 | 101 | // Single argument string literal, no space 102 | foo("bar"); 103 | 104 | // Inner grouping parens, no space 105 | if (!("foo" in obj)) { 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/false/input.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if (condition) { 3 | // statements 4 | } 5 | 6 | while (condition) { 7 | // statements 8 | } 9 | 10 | for (var i = 0; i < 100; i++) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for (prop in object){ 17 | // statements 18 | } 19 | 20 | if (true) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo(arg1, argN) { 29 | 30 | } 31 | 32 | // Usage 33 | foo(arg1, argN); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square(number) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square( 10 ); 43 | 44 | // Really contrived continuation passing style 45 | function square(number, callback) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function(square) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function(number) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial(number) { 64 | if (number < 2) { 65 | return 1; 66 | } 67 | 68 | return number * factorial(number - 1); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar(options) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar( { a: "alpha" } ); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo( function( ) { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | } ); 90 | 91 | // Function accepting an array, no space 92 | foo( ["alpha", "beta"] ); 93 | 94 | // 2.C.1.2 95 | // Function accepting an object, no space 96 | foo( { 97 | a: "alpha", 98 | b: "beta" 99 | } ); 100 | 101 | // Single argument string literal, no space 102 | foo( "bar" ); 103 | 104 | // Inner grouping parens, no space 105 | if (!( "foo" in obj )) { 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/false/sample.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if (condition) { 3 | // statements 4 | } 5 | 6 | while (condition) { 7 | // statements 8 | } 9 | 10 | for (var i = 0; i < 100; i++) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for (prop in object) { 17 | // statements 18 | } 19 | 20 | if (true) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo(arg1, argN) { 29 | 30 | } 31 | 32 | // Usage 33 | foo(arg1, argN); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square(number) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square(10); 43 | 44 | // Really contrived continuation passing style 45 | function square(number, callback) { 46 | callback(number * number); 47 | } 48 | 49 | square(10, function(square) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function(number) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial(number) { 64 | if (number < 2) { 65 | return 1; 66 | } 67 | 68 | return number * factorial(number - 1); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar(options) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar({a: "alpha"}); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo(function() { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | }); 90 | 91 | // Function accepting an array, no space 92 | foo(["alpha", "beta"]); 93 | 94 | // 2.C.1.2 95 | // Function accepting an object, no space 96 | foo({ 97 | a: "alpha", 98 | b: "beta" 99 | }); 100 | 101 | // Single argument string literal, no space 102 | foo("bar"); 103 | 104 | // Inner grouping parens, no space 105 | if (!("foo" in obj)) { 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/false/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "spaces_in_brackets": false 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/hybrid/expected.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if ( condition ) { 3 | // statements 4 | } 5 | 6 | while ( condition ) { 7 | // statements 8 | } 9 | 10 | for ( var i = 0; i < 100; i++ ) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for ( prop in object ) { 17 | // statements 18 | } 19 | 20 | if ( true ) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo( arg1, argN ) { 29 | 30 | } 31 | 32 | // Usage 33 | foo( arg1, argN ); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square( number ) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square( 10 ); 43 | 44 | // Really contrived continuation passing style 45 | function square( number, callback ) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function( square ) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function( number ) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial( number ) { 64 | if ( number < 2 ) { 65 | return 1; 66 | } 67 | 68 | return number * factorial( number - 1 ); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar( options ) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar({ a: "alpha" }); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo(function() { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | }); 90 | 91 | foo(function() {}); 92 | 93 | // Function accepting an array, no space 94 | foo([ "alpha", "beta" ]); 95 | 96 | // 2.C.1.2 97 | // Function accepting an object, no space 98 | foo({ 99 | a: "alpha", 100 | b: "beta" 101 | }); 102 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/hybrid/input.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if (condition) { 3 | // statements 4 | } 5 | 6 | while (condition) { 7 | // statements 8 | } 9 | 10 | for (var i = 0; i < 100; i++) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for (prop in object) { 17 | // statements 18 | } 19 | 20 | if (true){ 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo(arg1, argN) { 29 | 30 | } 31 | 32 | // Usage 33 | foo(arg1, argN); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square(number) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square( 10 ); 43 | 44 | // Really contrived continuation passing style 45 | function square(number, callback) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function(square) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function(number) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial(number) { 64 | if (number < 2) { 65 | return 1; 66 | } 67 | 68 | return number * factorial(number - 1); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar(options) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar( {a: "alpha"} ); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo( function( ) { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | } ); 90 | 91 | foo(function(){}); 92 | 93 | // Function accepting an array, no space 94 | foo( ["alpha", "beta"] ); 95 | 96 | // 2.C.1.2 97 | // Function accepting an object, no space 98 | foo( { 99 | a: "alpha", 100 | b: "beta" 101 | } ); 102 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/hybrid/sample.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if ( condition ) { 3 | // statements 4 | } 5 | 6 | while ( condition ) { 7 | // statements 8 | } 9 | 10 | for ( var i = 0; i < 100; i++ ) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for ( prop in object ) { 17 | // statements 18 | } 19 | 20 | if ( true ) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo( arg1, argN ) { 29 | 30 | } 31 | 32 | // Usage 33 | foo( arg1, argN ); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square( number ) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square( 10 ); 43 | 44 | // Really contrived continuation passing style 45 | function square( number, callback ) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function( square ) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function( number ) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial( number ) { 64 | if ( number < 2 ) { 65 | return 1; 66 | } 67 | 68 | return number * factorial( number - 1 ); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar( options ) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar({ a: "alpha" }); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo(function() { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | }); 90 | 91 | foo(function(){}); 92 | 93 | // Function accepting an array, no space 94 | foo([ "alpha", "beta" ]); 95 | 96 | // 2.C.1.2 97 | // Function accepting an object, no space 98 | foo({ 99 | a: "alpha", 100 | b: "beta" 101 | }); 102 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/hybrid/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "spaces_in_brackets": "hybrid" 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/true/expected.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if ( condition ) { 3 | // statements 4 | } 5 | 6 | while ( condition ) { 7 | // statements 8 | } 9 | 10 | for ( var i = 0; i < 100; i++ ) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for ( prop in object ) { 17 | // statements 18 | } 19 | 20 | if ( true ) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo( arg1, argN ) { 29 | 30 | } 31 | 32 | // Usage 33 | foo( arg1, argN ); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square( number ) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square( 10 ); 43 | 44 | // Really contrived continuation passing style 45 | function square( number, callback ) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function( square ) { 50 | // callback statements 51 | } ); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function( number ) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial( number ) { 64 | if ( number < 2 ) { 65 | return 1; 66 | } 67 | 68 | return number * factorial( number - 1 ); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar( options ) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar( { a: "alpha" } ); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo( function( ) { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | } ); 90 | 91 | // Function accepting an array, no space 92 | foo( [ "alpha", "beta" ] ); 93 | 94 | // 2.C.1.2 95 | // Function accepting an object, no space 96 | foo( { 97 | a: "alpha", 98 | b: "beta" 99 | } ); 100 | 101 | // Single argument string literal, no space 102 | foo( "bar" ); 103 | 104 | // Inner grouping parens, no space 105 | if ( !( "foo" in obj ) ) { 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/true/input.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if (condition) { 3 | // statements 4 | } 5 | 6 | while (condition) { 7 | // statements 8 | } 9 | 10 | for (var i = 0; i < 100; i++) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for (prop in object) { 17 | // statements 18 | } 19 | 20 | if (true) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo(arg1, argN) { 29 | 30 | } 31 | 32 | // Usage 33 | foo(arg1, argN); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square(number) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square(10); 43 | 44 | // Really contrived continuation passing style 45 | function square(number, callback) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function(square) { 50 | // callback statements 51 | }); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function(number) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial(number) { 64 | if (number < 2) { 65 | return 1; 66 | } 67 | 68 | return number * factorial(number - 1); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar(options){ 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar({ a: "alpha" }); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo(function(){ 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | }); 90 | 91 | // Function accepting an array, no space 92 | foo(["alpha", "beta"]); 93 | 94 | // 2.C.1.2 95 | // Function accepting an object, no space 96 | foo({ 97 | a: "alpha", 98 | b: "beta" 99 | }); 100 | 101 | // Single argument string literal, no space 102 | foo("bar"); 103 | 104 | // Inner grouping parens, no space 105 | if (!("foo" in obj)){ 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/true/sample.js: -------------------------------------------------------------------------------- 1 | // 2.A.1.1 2 | if ( condition ) { 3 | // statements 4 | } 5 | 6 | while ( condition ) { 7 | // statements 8 | } 9 | 10 | for ( var i = 0; i < 100; i++ ) { 11 | // statements 12 | } 13 | 14 | var prop; 15 | 16 | for ( prop in object ) { 17 | // statements 18 | } 19 | 20 | if ( true ) { 21 | // statements 22 | } else { 23 | // statements 24 | } 25 | 26 | // 2.B.2.1 27 | // Named Function Declaration 28 | function foo( arg1, argN ) { 29 | 30 | } 31 | 32 | // Usage 33 | foo( arg1, argN ); 34 | 35 | // 2.B.2.2 36 | // Named Function Declaration 37 | function square( number ) { 38 | return number * number; 39 | } 40 | 41 | // Usage 42 | square( 10 ); 43 | 44 | // Really contrived continuation passing style 45 | function square( number, callback ) { 46 | callback( number * number ); 47 | } 48 | 49 | square( 10, function( square ) { 50 | // callback statements 51 | } ); 52 | 53 | // 2.B.2.3 54 | // Function Expression 55 | var square = function( number ) { 56 | // Return something valuable and relevant 57 | return number * number; 58 | }; 59 | 60 | // Function Expression with Identifier 61 | // This preferred form has the added value of being 62 | // able to call itself and have an identity in stack traces: 63 | var factorial = function factorial( number ) { 64 | if ( number < 2 ) { 65 | return 1; 66 | } 67 | 68 | return number * factorial( number - 1 ); 69 | }; 70 | 71 | // 2.B.2.4 72 | // Constructor Declaration 73 | function FooBar( options ) { 74 | 75 | this.options = options; 76 | } 77 | 78 | // Usage 79 | var fooBar = new FooBar( { a: "alpha" } ); 80 | 81 | fooBar.options; 82 | // { a: "alpha" } 83 | 84 | // 2.C.1.1 85 | // Functions with callbacks 86 | foo( function( ) { 87 | // Note there is no extra space between the first paren 88 | // of the executing function call and the word "function" 89 | } ); 90 | 91 | // Function accepting an array, no space 92 | foo( [ "alpha", "beta" ] ); 93 | 94 | // 2.C.1.2 95 | // Function accepting an object, no space 96 | foo( { 97 | a: "alpha", 98 | b: "beta" 99 | } ); 100 | 101 | // Single argument string literal, no space 102 | foo( "bar" ); 103 | 104 | // Inner grouping parens, no space 105 | if ( !( "foo" in obj ) ) { 106 | 107 | } 108 | -------------------------------------------------------------------------------- /test/cases/spaces_in_brackets/true/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "spaces_in_brackets": true 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/false/expected.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/false/input.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/false/sample.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/false/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "trim_trailing_whitespace": false 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/true/expected.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/true/input.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/true/sample.js: -------------------------------------------------------------------------------- 1 | var i; 2 | var fib = []; 3 | 4 | fib[0] = 0; 5 | fib[1] = 1; 6 | for(i=2; i<=10; i++) 7 | { 8 | // Next fibonacci number = previous + one before previous 9 | fib[i] = fib[i-2] + fib[i-1]; 10 | alert(fib[i]); 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/trim_trailing_whitespace/true/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "trim_trailing_whitespace": true 3 | } 4 | -------------------------------------------------------------------------------- /test/codepaint.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var should = require('should'); 4 | 5 | var codepaint = require('../codepainter'); 6 | 7 | 8 | describe('codepaint command', function() { 9 | 10 | describe('infer sub-command', function() { 11 | 12 | it('infers formatting style from a sample file', function (done) { 13 | var sample = path.resolve('test/inputs/sample.js'); 14 | var rs = fs.createReadStream(sample); 15 | codepaint.infer(rs, function (style) { 16 | style.should.not.be.undefined; 17 | done(); 18 | }); 19 | }); 20 | 21 | it('supports a ReadableStream as input', function(done) { 22 | var sample = path.resolve('test/inputs/sample.js'); 23 | codepaint.infer(fs.createReadStream(sample), function (style) { 24 | style.should.not.be.undefined; 25 | done(); 26 | }); 27 | }); 28 | }); 29 | 30 | describe('transform sub-command', function() { 31 | 32 | it('transforms an input file to an output file', function (done) { 33 | var input = path.resolve('test/inputs/input.js'); 34 | var options = { 35 | style: { 36 | indent_style: 'tab' 37 | }, 38 | output: path.resolve('tmp/out.js') 39 | }; 40 | codepaint.xform(input, options, function (err, xformed, skipped, errored) { 41 | should.not.exist(err); 42 | xformed.should.equal(1); 43 | skipped.should.equal(0); 44 | errored.should.equal(0); 45 | fs.unlink(options.output, function () { 46 | done(); 47 | }); 48 | }); 49 | }); 50 | }); 51 | 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /test/inputs/input.js: -------------------------------------------------------------------------------- 1 | var foo = 'bar'; 2 | -------------------------------------------------------------------------------- /test/inputs/sample.js: -------------------------------------------------------------------------------- 1 | var baz = 'qux'; 2 | --------------------------------------------------------------------------------