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.
$ 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
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
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.
Example usage:
codepaint.infer('**/**.js', {details: true}, function(inferredStyle) {
console.log(inferredStyle);
});
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:
Every time one style cascades over another.
transformer.on('cascade', cascade);
function cascade(styleBefore, styleToMerge, styleType) {
// code here
}
Every time a file is transformed.
transformer.on('transform', function(transformed, path) {
// code here
}
transformer.on('error', function(err, inputPath) {
// code here
}
When all transformations have taken place.
transformer.on('end', function(err, transformed, skipped, errored) {
// code here
}
Of course, none of these events will fire if you don't perform the transform:
transformer.transform(globs, options);
$ 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
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.
indent_style, indent_size, end_of_line, trim_trailing_whitespace and insert_final_newline.
Refer to EditorConfig's documentation for more information.
Specifies what kind of quoting you would like to use for string literals:
console.log("Hello world!"); // becomes console.log('Hello world!');
Adds proper escaping when necessary, obviously.
console.log('Foo "Bar" Baz'); // becomes console.log("Foo \"Bar\" Baz");
The auto setting infers the quoting with a precedence toward single mode.
console.log("Foo \"Bar\" Baz"); // becomes console.log('Foo "Bar" Baz');
console.log('Foo \'Bar\' Baz'); // becomes console.log("Foo 'Bar' Baz");
Specifies whether or not there should be a space between if/for/while and the following open paren:
If true:
if(x === 4) {} // becomes if (x === 4) {}
If false:
while (foo()) {} // becomes while(foo()) {}
Specifies whether or not there should be a space between the function
keyword and the following parens in anonymous functions:
function(x) {} // becomes function (x) {}
Specifies whether or not there should be spaces around operators such as
+,=,+=,>=,!==
.
x = 4; // becomes x=4;
a>=b; // becomes a >= b;
a>>2; // becomes 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' : // becomes case 'foo':
var x = {foo : 'bar'}; // becomes {foo: 'bar'}
break;
}
Hybrid mode is mostly like the true setting, except it behaves as
false on operators *,/,%
:
var x = 4 * 2 + 1 / 7; // becomes var x = 4*2 + 1/7;
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) {} // becomes if ( x === 4 ) {}
If false:
if ( x === 4 ) {} // becomes if (x === 4)
The hybrid setting mostly reflects Idiomatic style. Refer to Idiomatic Style Manifesto.
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`
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.
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:
# Example usage: gc "initial commit"
alias gc=codepaint_git_commit
codepaint_git_commit() {
# 1. Gets a list of .js files in git staging and sends the list to CodePainter.
# 2. CodePainter with the -e flag applies rules defined in your EditorConfig file(s).
# 3. After CodePainter is done, your args are passed to the `git commit` command.
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).
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.
Released under the MIT license.