Code Painter
Code Painter is a JavaScript beautifier that can transform JavaScript files
into the formatting style of your choice. Style settings can be supplied via
predefined styles, a custom JSON file, command line settings, EditorConfig
settings or it can even be inferred from one or more sample files. For example,
you could provide a code snippet from the same project with which the new code
is intended to integrate.
It uses the excellent Esprima parser by Ariya Hidayat and his
contributors — thanks!
The name is inspired by Word's Format Painter, which does a similar job for
rich text.
Requirements
Code Painter requires Node.js version 0.10.6 or above.
Installation
$ npm install codepainter
To access the command globally, do a global install:
$ npm install -g codepainter
*nix users might also have to add the following to their .bashrc file:
PATH=$PATH:/usr/local/share/npm/bin
CLI Usage
You can see the usage in the CLI directly by typing codepaint
or
codepaint --help
.
$ codepaint --help
Code Painter beautifies JavaScript.
Usage: codepaint [options] <command>
Commands:
infer [options] <globs>... Infer formatting style from file(s)
xform [options] <globs>... Transform file(s) to specified style
Options:
-h, --help output help information
-V, --version output version information
$ codepaint infer --help
Infer formatting style from file(s)
Usage: infer [options] <globs>...
Options:
-h, --help output help information
-d, --details give a detailed report with trend scores
Examples:
$ codepaint infer "**/*.js"
$ codepaint infer "**/*view.js" "**/*model.js"
$ codepaint infer %s "**/*.js" -m
$ codepaint infer %s "**/*.js" -e inferred.json
$ codepaint xform --help
Transform file(s) to specified formatting style
Usage: xform [options] <globs>...
Options:
-h, --help output help information
-i, --infer <glob> code sample(s) to infer
-p, --predef <name> cascade predefined style (e.g., idiomatic)
-j, --json <path> cascade JSON style over predef style
-s, --style <key>=<value> cascade explicit style over JSON
-e, --editor-config cascade EditorConfig style over all others
Examples:
$ codepaint xform "**/*.js"
$ codepaint xform "**/*view.js" "**/*model.js"
$ codepaint xform %s "**/*.js" -i sample.js
$ codepaint xform %s "**/*.js" -p idiomatic
$ codepaint xform %s "**/*.js" -j custom.json
$ codepaint xform %s "**/*.js" -s quote_type=null
$ codepaint xform %s "**/*.js" -s indent_style=space -s indent_size=4
$ codepaint xform %s "**/*.js" -e
Library Usage
var codepaint = require('codepainter');
Library usage is intended to be every bit the same as CLI usage, so you can
expect the same options and arguments that the CLI requires.
.infer(< path|glob|globs|ReadableStream >[,options][,callback])
Example usage:
codepaint.infer('**/**.js', {details: true}, function(inferredStyle) {
console.log(inferredStyle);
});
.xform(< path|glob|globs|ReadableStream >[,options][,callback])
Example usage:
codepaint.xform('input.js', {indent_size: 4}, function(err, xformed, skipped, errored){
if (err) {
throw err;
}
console.log('transformed:', xformed);
console.log('skipped:', skipped);
console.log('errored:', errored);
});
The following example infers formatting style from sample.js
and uses that
inferred style to transform all .js files under the current directory.
codepaint.infer('sample.js', function(inferredStyle) {
codepainter.xform('**/**.js', {style: inferredStyle});
});
'sample.js' could also be an array or any readable stream. transform
is an
alias for the xform
method. You can use either one.
Great, so that's all nice and simple, but maybe you want to do something with
the output. We start by creating an instance of the Transformer class.
var Transformer = require('codepainter').Transformer;
var transformer = new Transformer();
Now, we can listen to any of the following events:
cascade
Every time one style cascades over another.
transformer.on('cascade', cascade);
function cascade(styleBefore, styleToMerge, styleType) {
}
transform
Every time a file is transformed.
transformer.on('transform', function(transformed, path) {
}
error
transformer.on('error', function(err, inputPath) {
}
end
When all transformations have taken place.
transformer.on('end', function(err, transformed, skipped, errored) {
}
Of course, none of these events will fire if you don't perform the transform:
transformer.transform(globs, options);
CLI Examples
$ codepaint infer "**/*.js"
Infers formatting style from all .js files under the current directory into a
single JSON object, which you can pipe out to another file if you want. It can
then be used in a transformation (below).
$ codepaint xform "**/*.js"
This doesn't transform any files, but it does show you how many files would be
affected by the glob you've provided. Globs absolutely must be in quotes or
you will experience unexpected behavior!
$ codepaint xform -i infer.js "**/*.js"
Transforms all .js files under the current directory with the formatting style
inferred from infer.js
$ codepaint xform -p idiomatic "**/*.js"
Transforms all .js files under the current directory with a Code Painter
pre-defined style. In this case, Idiomatic. The only other pre-defined styles
available at this time are mediawiki and hautelook.
$ codepaint xform -j custom.json "**/*.js"
Transforms all .js files under the current directory with a custom style in
JSON format.
$ codepaint xform -s indent_style=space -s indent_size=4 "**/*.js"
Transforms all .js files under the current directory with 2 settings:
indent_style=space
and indent_size=4
. You can specify as many settings as
you want and you can set values to null
to disable them.
$ codepaint xform -e "**/*.js"
Transforms all .js files under the current directory with the EditorConfig
settings defined for each individual file.
Refer to EditorConfig Core Installation for installation instructions and
EditorConfig for more information, including how to define and use
.editorconfig
files.
$ codepaint xform -i infer.js -p idiomatic -j custom.json
-s end_of_line=null -e "**/*.js"
As you can see, you can use as many options as you want. Code Painter will
cascade your styles and report how the cascade has been performed, like so:
Inferred style:
+ indent_style = tab
+ insert_final_newline = true
+ quote_type = auto
+ space_after_anonymous_functions = false
+ space_after_control_statements = false
+ spaces_around_operators = false
+ trim_trailing_whitespace = false
+ spaces_in_brackets = false
hautelook style:
* indent_style = space
+ indent_size = 4
* trim_trailing_whitespace = true
+ end_of_line = lf
= insert_final_newline = true
= quote_type = auto
* spaces_around_operators = true
= space_after_control_statements = true
= space_after_anonymous_functions = false
* spaces_in_brackets = false
Supplied JSON file:
* space_after_control_statements = true
= indent_style = space
* indent_size = 3
Inline styles:
x end_of_line = null
Editor Config:
+ applied on a file-by-file basis
...........................
REPORT: 27 files transformed
Supported Style Properties
codepaint: false
Tells CodePainter to skip the file (no formatting). This property really
only makes sense if you are using the --editor-config
CLI option. This
allows you to, for example, skip a vendor scripts directory.
EditorConfig properties
indent_style, indent_size, end_of_line,
trim_trailing_whitespace and insert_final_newline.
Refer to EditorConfig's documentation for more information.
quote_type: single, double, auto
Specifies what kind of quoting you would like to use for string literals:
console.log("Hello world!");
Adds proper escaping when necessary, obviously.
console.log('Foo "Bar" Baz');
The auto setting infers the quoting with a precedence toward single
mode.
console.log("Foo \"Bar\" Baz");
console.log('Foo \'Bar\' Baz');
space_after_control_statements: true, false
Specifies whether or not there should be a space between if/for/while and
the following open paren:
If true:
if(x === 4) {}
If false:
while (foo()) {}
space_after_anonymous_functions: true, false
Specifies whether or not there should be a space between the function
keyword and the following parens in anonymous functions:
function(x) {}
spaces_around_operators: true, false, hybrid
Specifies whether or not there should be spaces around operators such as
+,=,+=,>=,!==
.
x = 4;
a>=b;
a>>2;
Unary operators !,~,+,-
are an exception to the rule; thus, no spaces
are added. Also, any non-conditional :
operators do not receive a space
(i.e., the switch...case operator and property identifiers):
switch (someVar) {
case 'foo' :
var x = {foo : 'bar'};
break;
}
Hybrid mode is mostly like the true setting, except it behaves as
false on operators *,/,%
:
var x = 4 * 2 + 1 / 7;
spaces_in_brackets: true, false, hybrid
Specifies whether or not there should be spaces inside brackets, which
includes (),[],{}
. Empty pairs of brackets will always be shortened.
If true:
if (x === 4) {}
If false:
if ( x === 4 ) {}
The hybrid setting mostly reflects Idiomatic style. Refer to
Idiomatic Style Manifesto.
Pipes and Redirects
On a unix command-line, you can transform a file from the stdin stream:
$ codepaint xform -s indent_size=2 < input.js
The stdout stream works a bit differently. Since Code Painter can transform
multiple files via glob syntax, it wouldn't make sense to output the
transformations of all those files to a single stream. Instead, only if you
are using stdin as input and no -o, --output
option is provided will Code
Painter send the transformation to the stdout stream:
$ codepaint xform -s indent_size=2 < input.js > output.js
Piping is supported as well:
$ codepaint xform -s indent_size=2 < input.js | othercommand`
Git Clean and Smudge Filters
Because Code Painter supports stdin and stdout streams, as explained above,
Git "clean" and "smudge" filters can be used as well.
CAUTION: My personal experience has shown inconsistent results, so use with
caution! Also, please contact me if you figure out how to do this without any
hiccups.
First, change your .gitattributes
file to use your new filter. We'll call it
"codepaint".
*.js filter=codepaint
Then, tell Git what the "codepaint" filter does. First, we will convert code
to tabs upon checkout with the "smudge" filter:
$ git config filter.codepaint.smudge "codepaint xform -s indent_style=tab"
Then, upon staging of files with the Git "clean" filter, the style is restored
to spaces and cleaned to reflect any other style preferences you may have set:
$ git config filter.codepaint.clean "codepaint xform -p style.json"
This allows you to work in the indentation of your preference without stepping
on anyone's toes and checking in inconsistent indentation. Or maybe you have
your own preference for spaces around operators? Smudge it to your preference
and clean it to your company's formatting style.
WARNING: Git "clean" and "smudge" filters are bypassed with GitHub for
Windows.
Refer to Git's documentation for more information on Git "smudge" and
"clean" filters.
Recommended Use
It is highly recommended that you use the EditorConfig approach to painting
your code. To do so, do the following:
Place an .editorconfig
file at your project root. Refer to this
project's .editorconfig file for a point of reference as to how this
might look. You can also scatter .editorconfig
files elsewhere
throughout your project to prevent Code Painter from doing any
transformations (e.g., your vendor scripts folders). In this case, the
.editorconfig
file would simply read: codepaint = false
.
Specify Code Painter in your devDependencies section of your package.json:
{
"devDependencies": {
"codepainter": "~0.3.15"
}
}
Define a codepaint
script in the scripts section of your package.json:
{
"scripts": {
"codepaint": "node node_modules/codepainter/bin/codepaint xform -e **/**.js"
}
}
If you have Code Painter installed globally, the command is as simple as:
{
"scripts": {
"codepaint": "codepaint xform -e **/**.js"
}
}
But Code Painter wouldn't install globally by default, so the first
approach is the recommended one. Then, you can run Code Painter on your
entire project, consistently, with the following command:
$ npm run-script codepaint
You could run codepaint
manually every time you want to do it, but you
might find this next .bashrc
shortcut more useful. The idea is to run
this gc
alias to a newly-defined codepaint_git_commit
function. This,
you do instead of running git commit
. The caveat is that you need to
stage your changes with git add
before doing so. This is because the
command runs codepaint
only on staged .js
files. Aside from this
caveat, you can commit things mostly the same as you were used to before.
Now, gc
can paint your code before a commit and bail-out of the commit
if there are issues with the process (e.g., JavaScript parse errors). The
idea of formatting code before a commit is definitely controversial, but
if you choose to do so anyway, here's the neat trick to put in your
.bashrc
file:
alias gc=codepaint_git_commit
codepaint_git_commit() {
jsfiles=$(git diff --name-only --cached | egrep '\.js$')
if [ $jsfiles ]; then
./node_modules/codepainter/bin/codepaint xform -e $jsfiles
fi
git commit -m "$@"
}
You could also compare Code Painter's output with the original file on a Git
pre-commit hook and reject the commit if the files don't match. Let's be real
though. This would happen almost every time you commit and it would be a
royal pain in your workflow.
There are so many ways you could use Code Painter. How do you prefer to use
Code Painter? Feel free to contact me, Jed, with tips or suggestions. See
package.json for contact information).
Enforcing
Code Painter can be used to enforce a formatting style in a number of creative
ways. To fail Travis CI if code does not comply with your organization's
style guide, the process would work something like this:
- Run Code Painter on the code base.
- Fail Travis if any file changes are detected. This encourages developers
to run Code Painter before pushing code.
Running Code Painter with Travis is as simple as adding the command to the
before_script
section of your .travis.yml
file:
before_script:
- node node_modules/codepainter/bin/codepaint xform -e "**/**.js"
Notice I didn't use the command npm run-script codepaint
. This is because
there were issues with the double-quoted glob being processed. If you find a
way around this, please let me know.
Next, you need to create a node script that exits the node process with a
non-zero code if any changes are detected. This, we do with git diff
:
var clc = require('cli-color');
var spawn = require('child_process').spawn;
var git = spawn('git', ['diff', '--name-only']);
git.stdout.setEncoding('utf8');
git.stdout.on('data', exitWithErrorIfFilesHaveChanged);
function exitWithErrorIfFilesHaveChanged(data) {
console.log();
console.log(clc.red('Error: The following files do not conform to the CompanyX style guide:'));
console.log(data);
process.exit(1);
}
Finally, you can add this script to your .travis.yml
file in the script
section:
script:
- node gitdiff.js
Violä! Travis should now fail if code does not comply with your organization's
style guide.
License
Released under the MIT license.