├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── fixmyjs ├── lib ├── cli.js ├── index.js ├── legacy.js └── rules │ ├── camelCase.js │ ├── curly.js │ ├── debugger.js │ ├── delete.js │ ├── dotNotation.js │ ├── emptyStatement.js │ ├── eqeqeq.js │ ├── index.js │ ├── initUndefined.js │ ├── invalidConstructor.js │ ├── isNaN.js │ ├── multiVar.js │ ├── newSideEffects.js │ ├── parseInt.js │ ├── snake_case.js │ ├── updateExpression.js │ └── useLiteral.js ├── package.json └── test ├── add-parentheses-to-constructors-test.js ├── array-literal-test.js ├── asi-test.js ├── camelCase-test.js ├── case-test.js ├── comments-must-stay-test.js ├── curly-test.js ├── debugger-test.js ├── delete-var-test.js ├── dependencies.js ├── dot-notation-test.js ├── empty-statements-test.js ├── eqeqeq-test.js ├── immed-test.js ├── init-undefined-test.js ├── invalid-construction-test.js ├── invoke-constructor-test.js ├── isNaN-test.js ├── leading-decimal-test.js ├── multivar-for-test.js ├── multivar-test.js ├── new-as-side-effects-test.js ├── object-literal-test.js ├── parseInt-test.js ├── shebang-test.js ├── snake_case-test.js ├── trailing-commas-test.js ├── trailing-decimal-test.js ├── unnecessary-semicolon-test.js ├── update-expression-test.js └── validate-rules-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | docs 2 | node_modules 3 | npm-debug.log 4 | sample.js 5 | TODO 6 | .*.swp 7 | coverage 8 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | coverage 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true, 3 | "boss": true, 4 | "curly": true, 5 | "eqeqeq": false, 6 | "eqnull": true, 7 | "esnext": true, 8 | "expr": true, 9 | "forin": true, 10 | "immed": true, 11 | "laxbreak": true, 12 | "newcap": false, 13 | "noarg": true, 14 | "node": true, 15 | "nonew": true, 16 | "plusplus": true, 17 | "quotmark": "single", 18 | "strict": false, 19 | "undef": true, 20 | "unused": true 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: npm test && npm run coverage && cat ./coverage/lcov.info | coveralls 3 | node_js: 4 | - '0.10' 5 | - '0.11' 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.0.2 2 | 3 | * Removes the validation of rules like `indentpref` and `quotmark` 4 | 5 | # v1.0.1 6 | 7 | * Fixes bug with camelCase in MemberExpressions 8 | 9 | # v1.0.0 10 | 11 | Initial Release 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * Indent 2 spaces 4 | * Always use single quotes 5 | * Use ASI (no semicolons unless needed) 6 | * Maximum line length is 80 characters 7 | * Write tests 8 | * Code must pass `npm test` 9 | 10 | ## Whitespace 11 | 12 | * Space after `if` `while` `for` etc. Key rule: if it's a function call then no space, if it is a keyword then use a space 13 | * Space after `function` 14 | * No space for function declarations or named functions: `function foo() { }` 15 | 16 | ## Misc Styles 17 | 18 | * One var per line, and try to declare them at the top of the function 19 | * `//` for comments 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-present by Josh Perez 2 | http://josh.mit-license.org 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [fixmyjs](https://fixmyjs.com) 2 | 3 | [![Join the chat at https://gitter.im/jshint/fixmyjs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jshint/fixmyjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | > Meant to automatically fix your JavaScript errors in a non-destructive way. 6 | 7 | [![Build Status](https://secure.travis-ci.org/jshint/fixmyjs.svg)](http://travis-ci.org/jshint/fixmyjs) 8 | [![Coverage Status](https://img.shields.io/coveralls/jshint/fixmyjs.svg?style=flat)](https://coveralls.io/r/jshint/fixmyjs) 9 | [![NPM version](https://badge.fury.io/js/fixmyjs.svg)](http://badge.fury.io/js/fixmyjs) 10 | [![Dependency Status](https://david-dm.org/jshint/fixmyjs.svg)](https://david-dm.org/jshint/fixmyjs) 11 | [![devDependency Status](https://david-dm.org/jshint/fixmyjs/dev-status.svg)](https://david-dm.org/jshint/fixmyjs#info=devDependencies) 12 | [![Download Count](https://img.shields.io/npm/dm/fixmyjs.svg?style=flat)](https://www.npmjs.com/package/fixmyjs) 13 | 14 | ## Installing 15 | 16 | ``` 17 | npm install fixmyjs -g 18 | ``` 19 | 20 | ## Usage 21 | 22 | ``` 23 | fixmyjs your_file.js 24 | ``` 25 | 26 | ### Programatically 27 | 28 | ```js 29 | var fixmyjs = require('fixmyjs') 30 | var stringFixedCode = fixmyjs.fix(stringOfCode, objectOfOptions) 31 | ``` 32 | 33 | 34 | ## Tools 35 | 36 | - [Atom plugin](https://github.com/sindresorhus/atom-fixmyjs) 37 | - [Brackets plugin](https://github.com/fyockm/brackets-fixmyjs) 38 | - [Gulp plugin](https://github.com/kirjs/gulp-fixmyjs) 39 | - [Grunt plugin](https://github.com/jonschlinkert/grunt-fixmyjs) 40 | - [Sublime plugin](https://github.com/addyosmani/sublime-fixmyjs) 41 | - [fixmyjs.com](http://fixmyjs.com) 42 | 43 | 44 | ## Options 45 | 46 | These options are mostly named after their JSHINT counterparts. 47 | 48 | ### Built in 49 | 50 | * `delete` - Removes deletion of variables 51 | * `emptyStatement` - Removes empty statements 52 | * `initUndefined` - Rewrites variable initializations to undefined 53 | * `invalidConstructor` - Does not allow you to initialize built-in primitive constructors 54 | * `isNaN` - Replaces equality to NaN with isNaN 55 | * `useLiteral` - Rewrites your primitives to use their literal form 56 | 57 | ### Truthy 58 | 59 | When these are set to true the options apply. 60 | 61 | * `camelcase` - Converts all identifiers to camelCase 62 | * `curly` - Adds curly braces to all statements that don't have them 63 | * `es3` - Adds a radix parameter to parseInt 64 | * `nonew` - Removes new when using it for side effects 65 | * `snakecase` - Convert all identifiers to snake_case 66 | * `multivar` - Replace single var with multi line var 67 | * `plusplus` - Converts `++` and `--` to `+= 1` || `-= 1` 68 | * `eqeqeq` - Enforce strict equality 69 | 70 | ### Falsy 71 | 72 | When these are set to false the options apply. 73 | 74 | * `debug` - Removes debugger statements 75 | * `sub` - Dot notation conversion 76 | 77 | 78 | ## Legacy Mode 79 | 80 | fixmyjs supports a `legacy` mode which can be used via the CLI and programatically. 81 | 82 | ### CLI 83 | 84 | ``` 85 | fixmyjs --legacy your_file.js 86 | ``` 87 | 88 | ### Programatically 89 | 90 | ```js 91 | var jshint = require('jshint').JSHINT 92 | var fixmyjs = require('fixmyjs') 93 | jshint(stringOfCode, objectOfOptions) 94 | var stringFixedCode = fixmyjs(jshint.data(), stringOfCode, objectOfOptions).run() 95 | ``` 96 | 97 | Legacy uses [JSHINT](https://github.com/jshint/jshint) to determine what needs to be fixed and then uses a combination of regular expressions and string replacements to non-destructively fix any errors. While non-legacy supports more options, it is more prone to being destructive since the JavaScript is rewritten by the program. 98 | 99 | ### Why is it legacy? 100 | 101 | We're planning on moving away from code string transformations and into transforming the AST directly because these rules are easier to write, maintain, and offers flexibility in terms of what can be supported. `2.0` release will have fixmyjs using [recast](https://github.com/benjamn/recast) which will make fixmyjs more performant and less destructive, [esformatter](https://github.com/millermedeiros/esformatter) will also be included to perform any style changes. 102 | 103 | 104 | ## License 105 | 106 | [MIT](https://github.com/jshint/fixmyjs/blob/master/LICENSE) 107 | -------------------------------------------------------------------------------- /bin/fixmyjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli') 3 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | var commander = require('commander') 2 | var diff = require('diff') 3 | var fixmyjs = require('../') 4 | var fs = require('fs') 5 | var fu = require('fu') 6 | var jshint = require('jshint').JSHINT 7 | var minimatch = require('minimatch') 8 | var path = require('path') 9 | var version = require('../package.json').version 10 | 11 | function removeJsComments(str) { 12 | return (str || '') 13 | .replace(/\/\*[\s\S]*(?:\*\/)/g, '') //everything between '/* */' 14 | .replace(/\/\/[^\n\r]*/g, '') //everything after '//' 15 | } 16 | 17 | function loadAndParseConfig(filePath) { 18 | if (typeof filePath === 'object') { 19 | return filePath 20 | } 21 | 22 | try { 23 | return fs.existsSync(filePath) 24 | ? JSON.parse(removeJsComments(fs.readFileSync(filePath, 'utf-8'))) 25 | : {} 26 | } catch (ex) { 27 | console.error('Error opening config file ' + filePath) 28 | console.error(ex) 29 | process.exit(1) 30 | } 31 | } 32 | 33 | function mergeConfig(a, b) { 34 | var config = fu.merge({}, a) 35 | Object.keys(b).forEach(function (key) { 36 | if (key == 'predef') { 37 | config.predef = fu.concat(config.predef || [], b.predef) 38 | } else { 39 | config[key] = b[key] 40 | } 41 | }) 42 | return config 43 | } 44 | 45 | function getConfigAtPath(fullpath) { 46 | return loadAndParseConfig(fullpath) 47 | } 48 | 49 | function getConfig(dir) { 50 | return loadAndParseConfig(path.join(dir, '.jshintrc')) 51 | } 52 | 53 | function getIgnore(dir) { 54 | var PATH_TO_IGNORE = path.join(dir, '.jshintignore') 55 | var ignoreRules = fs.existsSync(PATH_TO_IGNORE) 56 | ? fu.compact(fs.readFileSync(PATH_TO_IGNORE, 'utf-8').split('\n')) 57 | : [] 58 | 59 | return fu.map(function (ignoreRule) { 60 | return path.join(dir, ignoreRule) 61 | }, ignoreRules) 62 | } 63 | 64 | function printDiff(a, b) { 65 | if (a == b) { 66 | return 67 | } 68 | 69 | var DARK = '\x1b[90m' 70 | var GREEN = '\x1b[32m' 71 | var RED = '\x1b[31m' 72 | var RESET = '\x1b[39m' 73 | 74 | var df = diff.diffLines(a, b) 75 | var content = fu.map(function (n) { 76 | var line = df[n] 77 | if (line.removed) { 78 | return RED + line.value 79 | } else if (line.added) { 80 | return GREEN + line.value 81 | } else { 82 | return DARK + line.value 83 | } 84 | }, Object.keys(df)) 85 | console.log(content.join(RESET + '\n'), RESET) 86 | } 87 | 88 | function createPatch(fileName, a, b) { 89 | console.log(diff.createPatch(fileName, a, b, '', '')) 90 | } 91 | 92 | function isDir(fullpath) { 93 | try { 94 | return fs.statSync(fullpath).isDirectory() 95 | } catch (ex) { 96 | if (ex.code == 'ENOENT') { 97 | console.error(String(ex)) 98 | } 99 | return null 100 | } 101 | } 102 | 103 | function shouldIgnorePath(fullpath, ignore) { 104 | return fu.any(function (ignoreRule) { 105 | var fnmatch = minimatch(fullpath, ignoreRule, { nocase: true }) 106 | var lsmatch = Boolean( 107 | isDir(ignoreRule) && 108 | ignoreRule.match(/^[^\/]*\/?$/) && 109 | fullpath.match(new RegExp('^' + ignoreRule + '.*')) 110 | ) 111 | return !!(fnmatch || lsmatch) 112 | }, ignore) 113 | } 114 | 115 | function shouldLintFile(fileName, ignore) { 116 | return (/\.js$/).test(fileName) && !shouldIgnorePath(fileName, ignore) 117 | } 118 | 119 | function genFixForFile(file, config) { 120 | return function () { 121 | var content = fs.readFileSync(file).toString() 122 | var fixed = '' 123 | 124 | var fmjOptions = commander.indentPref 125 | ? fu.merge(config, { indentpref: commander.indentPref }) 126 | : config 127 | 128 | try { 129 | if (commander.legacy) { 130 | jshint(content, config) 131 | fixed = fixmyjs(jshint.data(), content, fmjOptions).run() 132 | } else { 133 | fixed = fixmyjs.fix(content, fmjOptions) 134 | } 135 | } catch (ex) { 136 | ex.stack = 'File: ' + file + '\n' + ex.stack 137 | throw ex 138 | } 139 | 140 | if (commander.dryRun || commander.diff) { 141 | printDiff(content, fixed) 142 | } else if (commander.patch) { 143 | createPatch(file, content, fixed) 144 | } else { 145 | fs.writeFileSync(file, fixed, 'utf8') 146 | } 147 | 148 | if (!commander.silent) { 149 | console.log('\u2713 ' + path.basename(file) + ' done.') 150 | } 151 | 152 | return content === fixed 153 | } 154 | } 155 | 156 | function traverseFiles(_, fileName) { 157 | var fullpath = path.resolve(fileName) 158 | 159 | switch (isDir(fullpath)) { 160 | case true: 161 | if (shouldIgnorePath(fullpath, _.ignore)) { 162 | return [] 163 | } 164 | var ignore = fu.concat(_.ignore, getIgnore(fullpath)) 165 | var config = mergeConfig(_.config, getConfig(fullpath)) 166 | return fu.concatMap(function (x) { 167 | return traverseFiles({ 168 | ignore: ignore, 169 | config: config 170 | }, path.join(fileName, x)) 171 | }, fs.readdirSync(fullpath)) 172 | case false: 173 | return shouldLintFile(fullpath, _.ignore) 174 | ? [genFixForFile(fullpath, _.config)] 175 | : [] 176 | case null: 177 | return [fu.apply(function () { return false })] 178 | } 179 | } 180 | 181 | function getUserHome() { 182 | return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE 183 | } 184 | 185 | function cli() { 186 | var SUCCESS = 0 187 | var ERROR = 1 188 | 189 | var findFiles = fu.curry(traverseFiles, { 190 | ignore: [], 191 | config: mergeConfig( 192 | getConfig(getUserHome()), 193 | commander.config 194 | ? getConfigAtPath(commander.config) 195 | : {} 196 | ) 197 | }) 198 | var filesToLint = fu.concatMap(findFiles, commander.args) 199 | 200 | process.exit(commander.pass || fu.foldl(function (statusCode, fn) { 201 | return fn() 202 | ? statusCode == ERROR ? ERROR : SUCCESS 203 | : ERROR 204 | }, filesToLint, SUCCESS)) 205 | } 206 | 207 | commander 208 | .version(version) 209 | .option('-c, --config [.jshintrc]', 'Load your own config file') 210 | .option('-d, --diff', 'Similar to dry-run') 211 | .option('-l, --legacy', 'Use legacy fixmyjs') 212 | .option('-n, --indent-pref [tabs|spaces]', 'Your indentation preference') 213 | .option('-p, --patch', 'Output a patch file to stdout') 214 | .option('-r, --dry-run', 'Performs a dry-run and shows you a diff') 215 | .option('-s, --silent', 'A useless option') 216 | .option('-a, --pass', 'Always pass') 217 | .parse(process.argv) 218 | 219 | if (commander.args.length === 0) { 220 | commander.emit('help') 221 | } else { 222 | cli() 223 | } 224 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /*globals define, toString */ 2 | ;(function (name, definition) { 3 | 'use strict'; 4 | if (typeof define == 'function') { 5 | define(definition) 6 | } else if (typeof module != 'undefined' && module.exports) { 7 | module.exports = definition() 8 | } else { 9 | var Module = definition(), global = this, old = global[name] 10 | Module.noConflict = function () { 11 | global[name] = old 12 | return Module 13 | } 14 | global[name] = Module 15 | } 16 | }).call(this, 'fixMyJS', function () { 17 | var fixMyJS = require('./legacy') 18 | 19 | var esprima = require('esprima') 20 | var escodegen = require('escodegen') 21 | var fu = require('fu') 22 | var rules = require('./rules') 23 | 24 | var SHEBANG = /^\#\!.*/ 25 | 26 | var ESPRIMA_OPTIONS = { 27 | range: true, 28 | tokens: true, 29 | comment: true 30 | } 31 | 32 | function getRules(has) { 33 | var getRule = function (x) { return x[1] } 34 | var falseRule = function (x) { return has(x[0], false) } 35 | var trueRule = fu.comp(fu.not, falseRule) 36 | return fu.concat( 37 | rules.builtin, 38 | fu.map(getRule, fu.filter(falseRule, fu.intoArray(rules.aretrue))), 39 | fu.map(getRule, fu.filter(trueRule, fu.intoArray(rules.arefalse))) 40 | ) 41 | } 42 | 43 | function validateRules(rules) { 44 | if (rules.camelcase && rules.snakecase) { 45 | throw new Error('Cannot contain both camelcase and snakecase options') 46 | } 47 | return rules 48 | } 49 | 50 | function traverse(o, f, p) { 51 | var k 52 | var self = function (x) { return traverse(x, f, p) } 53 | for (k in o) { 54 | if (toString.call(o[k]) == '[object Object]') { 55 | o[k] = traverse(o[k], f, o) 56 | } else if (Array.isArray(o[k])) { 57 | o[k] = fu.concatMap(self, o[k]) 58 | } 59 | } 60 | return f(o, p) 61 | } 62 | 63 | function createIndent(n, indent) { 64 | return Array(Number(n) + 1).join(indent == 'spaces' ? ' ' : '\t') 65 | } 66 | 67 | function genHas(obj) { 68 | return function (key, fallback) { 69 | return obj.hasOwnProperty(key) && obj[key] !== undefined 70 | ? obj[key] 71 | : fallback 72 | } 73 | } 74 | 75 | fixMyJS.fix = function (code, config) { 76 | validateRules(config) 77 | 78 | var shebang = SHEBANG.exec(code) 79 | var ast = esprima.parse(code.replace(SHEBANG, ''), ESPRIMA_OPTIONS) 80 | var astWithComments = escodegen.attachComments( 81 | ast, ast.comments, ast.tokens) 82 | var has = genHas(config) 83 | var rules = getRules(has) 84 | var options = { 85 | format: { 86 | indent: { 87 | style: createIndent(has('indent', 2), has('indentpref', 'spaces')), 88 | base: 0 89 | }, 90 | json: false, 91 | renumber: false, 92 | quotes: has('quotmark', 'single'), 93 | escapeless: has('escapeless', false), 94 | parentheses: true, 95 | semicolons: !has('asi', false) 96 | }, 97 | parse: null, 98 | comment: true 99 | } 100 | var modifiedTree = traverse(astWithComments, function (node, parent) { 101 | return fu.foldl(function (node, f) { 102 | return f.hasOwnProperty(node.type) 103 | ? f[node.type](node, parent) 104 | : node 105 | }, rules, node) 106 | }) 107 | var generatedCode = escodegen.generate(modifiedTree, options) 108 | 109 | return shebang === null 110 | ? generatedCode 111 | : [shebang[0], generatedCode].join('\n') 112 | } 113 | 114 | fixMyJS.version = require('../package.json').version 115 | 116 | return fixMyJS 117 | }) 118 | -------------------------------------------------------------------------------- /lib/legacy.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Global object. 3 | var exports = this; 4 | 5 | // Code Object 6 | // This object is what manipulates the code that's passed 7 | // to be fixed. 8 | var Code = function (src) { 9 | this.src = src.split('\n'); 10 | this._src = this.src.slice(0); 11 | }; 12 | 13 | // Retrieves the code that was stored in the Object 14 | // 15 | // returns String 16 | Code.prototype.getCode = function () { 17 | return this.src.join('\n'); 18 | }; 19 | 20 | // The fix method fixes a certain line in the code. 21 | // 22 | // **fn** is the function which will be responsible for modifying the line 23 | // **o** is the JSHint object related to the error we're fixing 24 | // 25 | // returns the fixed line as a String 26 | Code.prototype.fix = function (fn, o) { 27 | var line = o.line; 28 | var result = fn.call(fn, this.src[line], o, this); 29 | this.src.splice.apply(this.src, [line, 1].concat(result.split('\n'))); 30 | return result; 31 | }; 32 | 33 | // This function keeps track of character changes. 34 | // As the code is modified via additions/deletions 35 | // the character positioning reported by JSHint is no 36 | // longer 100% accurate. This function will return the 37 | // position where the intended character is at. 38 | // 39 | // **r** is the JSHint object related to the error 40 | // 41 | // returns Number 42 | // 43 | // Tabs are special, they count as two characters in text 44 | // and as one character by the JSHint parser. 45 | // If there are tabs then indentation is important, we'll need to know 46 | // how many characters each tab is supposed to be worth. 47 | Code.prototype.getChr = function (r) { 48 | var lineNo = r.line; 49 | var tabs = this.src[lineNo].split('\t'); 50 | 51 | return r.character - ((tabs.length - 1) * (r.config.indent - 1)) - 1; 52 | }; 53 | 54 | 55 | // Fix Object 56 | // Contains all the methods that fix the various errors 57 | var fix = (function () { 58 | 59 | // These are helpers that a few of the errors share in common 60 | var helpers = { 61 | 62 | // Inserts a string within a string at a certain offset. 63 | // 64 | // **str** is the initial string 65 | // **offset** is a number where we'll be inserting 66 | // **newstr** is the string that will be inserted 67 | // 68 | // returns the modified String 69 | insertIntoString: function (str, offset, newstr) { 70 | var part1 = str.substr(0, offset); 71 | var part2 = str.substr(offset); 72 | 73 | return part1 + newstr + part2; 74 | }, 75 | 76 | replaceBetween: function (str, offset, fn) { 77 | var part1 = str.substr(0, offset); 78 | var part2 = str.substr(offset); 79 | 80 | return part1 + fn(part2); 81 | }, 82 | 83 | // Removes a certain character from the string 84 | // 85 | // **str** is the string 86 | // **pos** is the position in the string we'll be removing 87 | // 88 | // returns the modified String 89 | rmFromString: function (str, pos) { 90 | return str.slice(0, pos) + 91 | ''.substr(0, 1) + 92 | ''.slice(1) + 93 | str.slice(pos + 1); 94 | } 95 | }; 96 | 97 | // The following are the methods that make the fixes. 98 | // Each method is responsible for fixing one error. 99 | // 100 | // All methods have the same parameters 101 | // **str** is the string to fix 102 | // **o** is the JSHint object which holds the error information 103 | // **code** is the current Code object 104 | // 105 | // returns String 106 | var Fix = { 107 | 108 | // Adds a semicolon at the position specified by JSHint. 109 | // 110 | // For those that prefer to end their statements with 111 | // a semicolon fixmyjs will automatically insert a semicolon 112 | // wherever one is thought to be missing. 113 | // 114 | // Example: 115 | // 116 | // `var foo = 1` -> `var foo = 1;` 117 | addSemicolon: function (str, o, code) { 118 | var chr = code.getChr(o); 119 | // Protect against JSHINT bug: 120 | // https://github.com/jshint/jshint/issues/387 121 | var offset = chr - 6; 122 | if (offset > -1 && str.substr(offset, chr) === 'delete') { 123 | return str; 124 | } 125 | return helpers.insertIntoString(str, chr, ';'); 126 | }, 127 | 128 | // Adds a space at the position specified by JSHint. 129 | // 130 | // Related to the `white` option in JSHint. It is 131 | // meant for beautifying code and adds spaces where 132 | // spaces are supposed to be according to certain 133 | // authorities of the language. 134 | // 135 | // Example: 136 | // 137 | // `var a = function(){}` -> `var a = function () {}` 138 | addSpace: function (str, o, code) { 139 | var chr = code.getChr(o); 140 | if (chr < str.length) { 141 | return helpers.insertIntoString(str, chr, ' '); 142 | } 143 | 144 | return str; 145 | }, 146 | 147 | // Converts assignments from Object to Literal form. 148 | //+ arrayLiteral :: String -> String 149 | // FIXME r10 JSHINT 150 | arrayLiteral: function (str) { 151 | return str.replace(/new Array(\(\))?(?!.*new Array(\(\))?)/, '[]'); 152 | }, 153 | 154 | // Converts from square bracket notation to dot notation. 155 | // 156 | // Example: 157 | // 158 | // `person['name']` -> `person.name` 159 | // FIXME r10 JSHINT 160 | dotNotation: function (str, o) { 161 | var dot = o.a; 162 | var rx = new RegExp('\\[[\'"]' + 163 | dot + '[\'"]\\]?(?!.*\\[[\'"]' + 164 | dot + '[\'"]\\]?)'); 165 | 166 | return str.replace(rx, function () { 167 | return '.' + dot; 168 | }); 169 | }, 170 | 171 | // Immediate functions are executed within the parenthesis. 172 | // 173 | // By wrapping immediate functions in parenthesis you indicate 174 | // that the expression is the result of a function and not the 175 | // function itself. 176 | //+ immed :: String -> String 177 | // XXX 178 | immed: function (str) { 179 | var rx = /\)\((.*)\);/; 180 | var params; 181 | 182 | if (rx.test(str)) { 183 | params = rx.exec(str); 184 | str = str.replace(params[0], '(' + params[1] + '));'); 185 | } 186 | 187 | return str; 188 | }, 189 | 190 | // Auto-indents. Based on your preferences of `spaces` 191 | // or `tabs`. 192 | // 193 | // fixmyjs will not automatically indent your code unless 194 | // you have the `indentpref` option set to your preference 195 | // and `auto_indent` is set to true in your `.jshintrc` file. 196 | // 197 | // You may also want to configure the `indent` option to the 198 | // desired amount of characters you wish to indent. The default 199 | // set by JSHint is four. 200 | indent: function (str, o, code) { 201 | var indent = o.b; 202 | var found = code.getChr(o); 203 | var config = o.config; 204 | var tabs; 205 | var whitespace; 206 | var cutstr; 207 | if (config.auto_indent === true && config.indentpref) { 208 | switch (config.indentpref) { 209 | case 'spaces': 210 | whitespace = new Array(indent).join(' '); 211 | break; 212 | case 'tabs': 213 | tabs = (indent + 1) / config.indent; 214 | if (tabs > 0) { 215 | whitespace = new Array(tabs).join('\t'); 216 | } 217 | break; 218 | } 219 | 220 | cutstr = str.slice(0, found); 221 | 222 | // if the whitespace 'fix' should be on a newline 223 | if (found > 1 && !/^[\s]+$/.test(cutstr)) { 224 | // mutates the line count 225 | return cutstr.replace(/\s+$/, '') + 226 | '\n' + whitespace + 227 | str.slice(found).trim(); 228 | } 229 | 230 | str = whitespace + str.trim(); 231 | } 232 | 233 | return str; 234 | }, 235 | 236 | // Adds parens to constructors missing them during invocation. 237 | //+ invokeConstructor :: String -> String 238 | invokeConstructor: function (str, o, code) { 239 | var chr = code.getChr(o); 240 | var rx = new RegExp('^' + o.a); 241 | 242 | return helpers.replaceBetween(str, chr, function (rest) { 243 | return rest.replace(rx, function (a) { 244 | return a + '()'; 245 | }); 246 | }); 247 | }, 248 | 249 | // Adds a zero when there is a leading decimal. 250 | // 251 | // A leading decimal can be confusing if there isn't a 252 | // zero in front of it since the dot is used for calling 253 | // methods of an object. Plus it's easy to miss the dot. 254 | // 255 | // Example: 256 | // 257 | // `.5` -> `0.5` 258 | //+ leadingDecimal :: String -> String 259 | leadingDecimal: function (str, o) { 260 | return str.replace(/([\D] *)\.([\d]+)/g, function (a, b, c) { 261 | if (o.a === c) { 262 | return b + '0.' + c; 263 | } 264 | return a; 265 | }); 266 | }, 267 | 268 | // Removes spaces or tabs (depending on preference) when 269 | // both are present on the same line. 270 | mixedSpacesNTabs: function (str, o) { 271 | var config = o.config; 272 | var spaces; 273 | if (config.indentpref) { 274 | spaces = new Array(config.indent + 1).join(' '); 275 | 276 | if (config.indentpref === 'spaces') { 277 | str = str.replace(/\t/g, spaces); 278 | } else if (config.indentpref === 'tabs') { 279 | str = str.replace(new RegExp(spaces, 'g'), '\t'); 280 | } 281 | } 282 | 283 | return str; 284 | }, 285 | 286 | // You shouldn't delete vars. This will remove the delete statement 287 | // and instead set the variable to undefined. 288 | // 289 | // Example: `delete foo;` -> `foo = undefined;` 290 | //+ noDeleteVar :: String -> String 291 | // XXX 292 | noDeleteVar: function (str) { 293 | var rx = /delete ([\w$_]+)(?!.*delete [\w$_]+)/; 294 | return str.replace(rx, function (a, b) { 295 | return b + ' = undefined'; 296 | }); 297 | }, 298 | 299 | // Removes `new` when it's used as a statement. 300 | // Only works if option `nonew` is set to true. 301 | // 302 | // Example: `new Ajax()` -> `Ajax()` 303 | //+ noNew :: String -> String 304 | // FIXME r10 JSHINT 305 | noNew: function (str) { 306 | var rx = /new ([\w$_]+)(?!.*new [\w$_]+)/; 307 | return str.replace(rx, function (a, b) { 308 | return b; 309 | }); 310 | }, 311 | 312 | // Converts assignments from Object to Literal form. 313 | //+ objectLiteral :: String -> String 314 | // XXX 315 | objectLiteral: function (str) { 316 | return str.replace(/new Object(\(\))?(?!.*new Object(\(\))?)/, '{}'); 317 | }, 318 | 319 | // Removes `new` when attempting to use a function not meant to 320 | // be a constructor. 321 | // 322 | // Uses RegEx to determine where the error occurs. If there's a match 323 | // then we extract the 1st and 2nd value of the result of the RegExp 324 | // execution, and use them in String replace. 325 | // 326 | // Example: `new Number(16)` -> `Number(16)` 327 | //+ objNoConstruct :: String -> String 328 | // FIXME r10 JSHINT 329 | objNoConstruct: function (str) { 330 | var rx = /new (Number|String|Boolean|Math|JSON)/; 331 | var exec; 332 | if (rx.test(str)) { 333 | exec = rx.exec(str); 334 | str = str.replace(exec[0], exec[1]); 335 | } 336 | return str; 337 | }, 338 | 339 | // Uses isNaN function rather than comparing to NaN. 340 | // 341 | // It's the same reason you shouldn't compare with undefined. 342 | // NaN can be redefined. Although comparing to NaN is faster 343 | // than using the isNaN function. 344 | //+ useIsNaN :: String -> String 345 | // XXX 346 | useIsNaN: function (str) { 347 | var rx = /([a-zA-Z_$][0-9a-zA-Z_$]*)( )*(=|!)(=|==)( )*NaN/; 348 | var exec; 349 | 350 | if (rx.test(str)) { 351 | exec = rx.exec(str); 352 | 353 | if (exec) { 354 | str = str.replace( 355 | exec[0], 356 | (exec[3] === '!' ? '!': '') + 'isNaN(' + exec[1] + ')' 357 | ); 358 | } 359 | } 360 | 361 | return str; 362 | }, 363 | 364 | // Adds radix parameter to parseInt statements. 365 | // 366 | // Although this parameter is optional, it's good practice 367 | // to add it so that the function won't assume the number is 368 | // octal. 369 | // 370 | // In the example below we have a sample Credit Card security 371 | // code which is padded by a zero. By adding the radix parameter 372 | // we are telling the compiler the base of the number is being 373 | // passed. 374 | // 375 | // Example: 376 | // 377 | // `parseInt('0420')` -> `parseInt('0420', 10)` 378 | //+ radix :: String -> String 379 | // FIXME r10 JSHINT 380 | radix: function (str) { 381 | var rx = /parseInt\(([^,\)\(]+)\)/; 382 | var offset = 0; 383 | var exec; 384 | 385 | while ((exec = rx.exec(str.substr(offset))) !== null) { 386 | var limit = exec.index + exec[0].length; 387 | var newCode = 'parseInt(' + exec[1] + ', 10)'; 388 | var result = str.substr(0, exec.index + offset) 389 | + newCode 390 | + str.substr(limit + offset); 391 | str = result; 392 | offset = exec.index + offset + newCode.length; 393 | } 394 | return str; 395 | }, 396 | 397 | // Removes a Character from the String 398 | rmChar: function (str, o, code) { 399 | var chr = code.getChr(o); 400 | return helpers.rmFromString(str, chr); 401 | }, 402 | 403 | // Removes debugger statements. 404 | // 405 | // Debugger statements can be useful for debugging 406 | // but some browsers don't support them so they shouldn't 407 | // be in production. 408 | //+ rmDebugger :: String 409 | rmDebugger: function () { 410 | return ''; 411 | }, 412 | 413 | // Removes undefined when variables are initialized to it. 414 | // 415 | // It's not necessary to initialize variables to undefined since 416 | // they are already initialized to undefined by declaring them. 417 | // 418 | // Example: 419 | // 420 | // `var foo = undefined;` -> `var foo;` 421 | //+ rmUndefined :: String -> String 422 | rmUndefined: function (str, o) { 423 | return str.replace(/([^ ]*) *= *undefined */g, function (orig, name) { 424 | return name === o.a ? name : orig; 425 | }); 426 | }, 427 | 428 | // Removes any whitespace at the end of the line. 429 | // Trailing whitespace sucks. It must die. 430 | //+ rmTrailingWhitespace :: String -> String 431 | rmTrailingWhitespace: function (str) { 432 | return str.replace(/\s+$/g, ''); 433 | }, 434 | 435 | // Throws an error that too many errors were reported by JSHint. 436 | // JSHint has a maximum amount of errors it can handle before it barfs. 437 | // If we encounter this, we just throw and recommend that the applications 438 | // that use `fixmyjs` catch the error and either retry to fix the file or 439 | // ask the user what they would like to do. 440 | // 441 | // NOTE: In cases where there are many errors in the file the `TME` error 442 | // may be encountered and none of the errors reported are supported by fixmyjs 443 | // see: GH-31 444 | tme: function () { 445 | throw new Error('Too many errors reported by JSHint.'); 446 | }, 447 | 448 | // Removes a trailing decimal where it's not necessary. 449 | // 450 | // Example: 451 | // 452 | // `12.` -> `12` 453 | //+ trailingDecimal :: String -> String 454 | trailingDecimal: function (str, o) { 455 | return str.replace(/([\d]+)\./g, function (a, b) { 456 | if (b + '.' === o.a) { 457 | return b; 458 | } 459 | return a; 460 | }); 461 | }, 462 | 463 | // Wraps RegularExpression literals in parenthesis to 464 | // disambiguate the slash operator. 465 | // 466 | // Example: `return /hello/;` -> `return (/hello/);` 467 | //+ wrapRegExp :: String -> String 468 | wrapRegExp: function (str) { 469 | var rx = /\/(.*)\/\w?/; 470 | var result; 471 | 472 | if (rx.test(str)) { 473 | result = rx.exec(str); 474 | str = str.replace(rx, '(' + result[0] + ')'); 475 | } 476 | 477 | return str; 478 | } 479 | }; 480 | 481 | return Fix; 482 | }()); 483 | 484 | 485 | // All errors supported by fixmyjs. 486 | var errors = { 487 | 'E043': fix.tme, 488 | 'W008': fix.leadingDecimal, 489 | 'W009': fix.arrayLiteral, 490 | 'W010': fix.objectLiteral, 491 | 'W011': fix.rmChar, 492 | 'W013': fix.addSpace, 493 | 'W015': fix.indent, 494 | 'W019': fix.useIsNaN, 495 | 'W031': fix.noNew, 496 | 'W032': fix.rmChar, 497 | 'W033': fix.addSemicolon, 498 | 'W047': fix.trailingDecimal, 499 | 'W051': fix.noDeleteVar, 500 | 'W053': fix.objNoConstruct, 501 | 'W058': fix.invokeConstructor, 502 | 'W062': fix.immed, 503 | 'W065': fix.radix, 504 | 'W069': fix.dotNotation, 505 | 'W070': fix.rmChar, 506 | 'W080': fix.rmUndefined, 507 | 'W087': fix.rmDebugger, 508 | 'W092': fix.wrapRegExp, 509 | 'W099': fix.mixedSpacesNTabs, 510 | 'W102': fix.rmTrailingWhitespace 511 | }; 512 | 513 | // Give each error a function which will call the proper fix function 514 | Object.keys(errors).forEach(function (key) { 515 | var fn = errors[key]; 516 | errors[key] = function (r, code) { 517 | return code.fix(fn, r); 518 | }; 519 | }); 520 | 521 | 522 | // fixMyJS is part of the global object 523 | exports.fixMyJS = (function () { 524 | // Copies over the results into one of our own objects 525 | // we decrement r.line by one becuse Arrays start at 0. 526 | // and we pass the config object to r. 527 | function copyResults(result, config) { 528 | var r = {}; 529 | Object.keys(result).forEach(function (key) { 530 | r[key] = result[key]; 531 | }); 532 | r.line -= 1; 533 | r.config = config; 534 | return r; 535 | } 536 | 537 | // Calls the function responsible for fixing the error passed. 538 | function fixError(r, code) { 539 | return errors[r.code](r, code); 540 | } 541 | 542 | // Function used in forEach which fixes all errors passed 543 | // **code** is the Code object 544 | // **config** is the config object 545 | // returns a function which when iterated it copies over the results 546 | // so we can mutate data later and then call fixError. 547 | function fixErrors(code, config) { 548 | return function (result) { 549 | var r = copyResults(result, config); 550 | fixError(r, code); 551 | }; 552 | } 553 | 554 | // Used by fixMyJS function in order to sort the 555 | // errors so we can fix the code bottom-up and right-left 556 | function byPriority(a, b) { 557 | if (a.line === b.line) { 558 | return b.character - a.character; 559 | } 560 | 561 | return b.line - a.line; 562 | } 563 | 564 | // The fixMyJS function is what's returned to the 565 | // global object. 566 | // 567 | // **data** is the data from jshint.data() 568 | // **src** is the original src passed to JSHint 569 | // 570 | // returns an Object containing the API 571 | function fixMyJS(data, src, options) { 572 | var code = new Code(src); 573 | var warnings = data.errors || []; 574 | var results = []; 575 | var config = data.options || {}; 576 | var current = 0; 577 | 578 | // merge custom options into config 579 | if (options) { 580 | Object.keys(options).forEach(function (option) { 581 | config[option] = options[option]; 582 | }); 583 | } 584 | 585 | function resetResults() { 586 | var dupes = {}; 587 | // Filter out errors we don't support. 588 | // If the error is null then we immediately return false 589 | // Then we check for duplicate errors. Sometimes JSHint will complain 590 | // about the same thing twice. This is a safeguard. 591 | // Otherwise we return true if we support this error. 592 | results = warnings.filter(function (v) { 593 | if (!v) { 594 | return false; 595 | } 596 | 597 | var err = 'line' + v.line + 598 | 'char' + v.character + 599 | 'reason' + v.reason; 600 | 601 | if (dupes.hasOwnProperty(err)) { 602 | return false; 603 | } 604 | dupes[err] = v; 605 | 606 | if (v.hasOwnProperty('fixable')) { 607 | return v.fixable; 608 | } 609 | 610 | return (v.fixable = errors.hasOwnProperty(v.code)); 611 | }); 612 | 613 | // sorts errors by priority. 614 | results.sort(byPriority); 615 | } 616 | 617 | resetResults(); 618 | 619 | 620 | // fixMyJS API 621 | // 622 | // * getErrors 623 | // * getAllErrors 624 | // * getCode 625 | // * next 626 | // * fix 627 | // * getDetails 628 | // * run 629 | var api = { 630 | // returns are supported errors that can be fixed. 631 | getErrors: function () { 632 | return results.slice(0); 633 | }, 634 | 635 | getAllErrors: function () { 636 | return warnings.slice(0); 637 | }, 638 | 639 | // returns the current state of the code. 640 | getCode: function () { 641 | return code.getCode(); 642 | }, 643 | 644 | // Iterator method. Returns Boolean if there is a next item 645 | // 646 | // Example: 647 | // while (af.hasNext()) { 648 | // var a = af.next(); 649 | // } 650 | hasNext: function () { 651 | return (current < results.length); 652 | }, 653 | 654 | // Iterator method. Iterates through each error in the 655 | // Array and returns an Object with fix and getDetails methods. 656 | // if the end of the Array is reached then an error is thrown. 657 | // 658 | // fix function will fix the current error and return the state of the code. 659 | // getDetails will return a prototype of the current error's details 660 | next: function () { 661 | if (!this.hasNext()) { 662 | throw new Error('End of list.'); 663 | } 664 | 665 | var r = copyResults(results[current], config); 666 | var data = { 667 | fix: function () { 668 | fixError(r, code); 669 | return code.getCode(); 670 | }, 671 | fixVerbose: function () { 672 | return { 673 | original: code._src[r.line], 674 | replacement: fixError(r, code) 675 | }; 676 | }, 677 | getDetails: function () { 678 | return Object.create(r); 679 | } 680 | }; 681 | current += 1; 682 | return data; 683 | }, 684 | 685 | filterErrors: function (fn) { 686 | warnings = warnings.map(function (w) { 687 | w.fixable = fn(w); 688 | return w; 689 | }); 690 | resetResults(); 691 | return warnings.slice(0); 692 | }, 693 | 694 | // runs through all errors and fixes them. 695 | // returns the fixed code. 696 | // 697 | // **returnErrors** Boolean - true if you'd like an Array of all errors 698 | // with the proposed fix. 699 | // 700 | // returns the code String || an Array of JSHint errors. 701 | run: function (returnErrors) { 702 | if (returnErrors) { 703 | return warnings 704 | .slice(0) 705 | .sort(byPriority) 706 | .map(function (v) { 707 | v.fixable && (v.fix = fixError(copyResults(v, config), code)); 708 | return v; 709 | }); 710 | } else { 711 | results.forEach(fixErrors(code, config)); 712 | return code.getCode(); 713 | } 714 | }, 715 | 716 | runVerbose: function () { 717 | var lint = []; 718 | var dup = {}; 719 | var next; 720 | while (api.hasNext()) { 721 | next = api.next(); 722 | lint.push(copyResults(next.fixVerbose(), next.getDetails())); 723 | } 724 | return lint.reverse().filter(function (x) { 725 | if (dup.hasOwnProperty(x.original)) { 726 | return false; 727 | } 728 | x.line = x.config.line; 729 | dup[x.original] = x; 730 | return true; 731 | }); 732 | } 733 | }; 734 | 735 | return api; 736 | } 737 | 738 | return fixMyJS; 739 | }()); 740 | 741 | exports.fixMyJS.legacyVersion = '0.6.9'; 742 | 743 | // for node.js 744 | // if module is available, we export to it. 745 | if (typeof module !== 'undefined') { 746 | module.exports = exports.fixMyJS; 747 | } 748 | 749 | }.call(this)); 750 | -------------------------------------------------------------------------------- /lib/rules/camelCase.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Identifier: useCamelCase 3 | } 4 | 5 | var regex = /[a-z]_([a-z])/g 6 | 7 | function useCamelCase(node, parent) { 8 | if (!regex.test(node.name)) { 9 | return node 10 | } 11 | 12 | if (parent) { 13 | if (parent.type == 'Property') { 14 | return node 15 | } 16 | 17 | if (parent.type == 'MemberExpression' && parent.property === node) { 18 | return node 19 | } 20 | } 21 | 22 | return { 23 | type: 'Identifier', 24 | name: node.name.replace(regex, function (i) { 25 | return i[0] + i[2].toUpperCase() 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/rules/curly.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ForStatement: addCurly, 3 | WhileStatement: addCurly, 4 | IfStatement: addCurlyIfStmt 5 | } 6 | 7 | var fu = require('fu') 8 | 9 | function merge(node, properties) { 10 | return fu.foldl(function (node, x) { 11 | if (node.hasOwnProperty(x[0])) { 12 | node[x[0]] = x[1] 13 | } 14 | return node 15 | }, fu.intoArray(properties), node) 16 | } 17 | 18 | function wrapInCurlies(node) { 19 | return { 20 | type: 'BlockStatement', 21 | body: [node] 22 | } 23 | } 24 | 25 | function addCurly(node) { 26 | if (node.body.type == 'BlockStatement') { 27 | return node 28 | } 29 | 30 | return merge(node, { 31 | body: wrapInCurlies(node.body) 32 | }) 33 | } 34 | 35 | function addCurlyIfStmt(node) { 36 | var code_paths = [ 37 | ['consequent', node.consequent], 38 | ['alternate', node.alternate] 39 | ] 40 | 41 | var uncurlied = fu.filter(function (x) { 42 | return x[1] != null && 43 | !(x[0] == 'alternate' && x[1].type == 'IfStatement') && 44 | x[1].type != 'BlockStatement' 45 | }, code_paths) 46 | 47 | return uncurlied.length === 0 48 | ? node 49 | : merge(node, fu.intoObject(fu.map(function (x) { 50 | return [x[0], wrapInCurlies(x[1])] 51 | }, uncurlied))) 52 | } 53 | -------------------------------------------------------------------------------- /lib/rules/debugger.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DebuggerStatement: rmDebuggerStmt 3 | } 4 | 5 | function rmDebuggerStmt() { 6 | return [] 7 | } 8 | -------------------------------------------------------------------------------- /lib/rules/delete.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | UnaryExpression: rmDelete 3 | } 4 | 5 | // You shouldn't delete vars. This will remove the delete statement 6 | // and instead set the variable to undefined. 7 | 8 | function rmDelete(node) { 9 | if (node.operator != 'delete') { 10 | return node 11 | } 12 | 13 | if (node.argument.type != 'Identifier') { 14 | return node 15 | } 16 | 17 | return { 18 | type: 'AssignmentExpression', 19 | operator: '=', 20 | left: node.argument, 21 | right: { type: 'Identifier', name: 'undefined' } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/rules/dotNotation.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MemberExpression: rewriteDotNotation 3 | } 4 | 5 | var fu = require('fu') 6 | 7 | var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/ 8 | var keywords = [ 9 | 'this', 10 | 'function', 11 | 'if', 12 | 'return', 13 | 'var', 14 | 'else', 15 | 'for', 16 | 'new', 17 | 'arguments', 18 | 'in', 19 | 'typeof', 20 | 'while', 21 | 'case', 22 | 'break', 23 | 'try', 24 | 'catch', 25 | 'delete', 26 | 'throw', 27 | 'switch', 28 | 'continue', 29 | 'default', 30 | 'instanceof', 31 | 'do', 32 | 'void', 33 | 'finally', 34 | 'with', 35 | 'debugger', 36 | 'eval', 37 | 'implements', 38 | 'interface', 39 | 'package', 40 | 'private', 41 | 'protected', 42 | 'public', 43 | 'static', 44 | 'yield', 45 | 'let', 46 | 'class', 47 | 'enum', 48 | 'export', 49 | 'extends', 50 | 'import', 51 | 'super' 52 | ] 53 | 54 | function rewriteDotNotation(node) { 55 | if (node.computed === false || node.property.type != 'Literal') { 56 | return node 57 | } 58 | 59 | if (validIdentifier.test(node.property.value) && 60 | !fu.elem(node.property.value, keywords)) { 61 | return { 62 | type: 'MemberExpression', 63 | computed: false, 64 | object: node.object, 65 | property: { 66 | type: 'Identifier', 67 | name: node.property.value 68 | } 69 | } 70 | } else { 71 | return node 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/rules/emptyStatement.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | EmptyStatement: rmEmpty 3 | } 4 | 5 | function rmEmpty(node, parent) { 6 | if (parent) { 7 | switch (parent.type) { 8 | case 'ForStatement': 9 | case 'IfStatement': 10 | case 'WhileStatement': 11 | return node 12 | } 13 | } 14 | return [] 15 | } 16 | -------------------------------------------------------------------------------- /lib/rules/eqeqeq.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | BinaryExpression: toStrictEquality 3 | } 4 | 5 | function toStrictEquality(node) { 6 | if (node.operator != '==' && node.operator != '!=') { 7 | return node 8 | } 9 | 10 | return { 11 | type: 'BinaryExpression', 12 | operator: node.operator == '==' ? '===' : '!==', 13 | left: node.left, 14 | right: node.right 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/rules/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | builtin: [ 3 | require('./delete'), 4 | require('./emptyStatement'), 5 | require('./initUndefined'), 6 | require('./invalidConstructor'), 7 | require('./isNaN'), 8 | require('./useLiteral'), 9 | ], 10 | aretrue: { 11 | camelcase: require('./camelCase'), 12 | curly: require('./curly'), 13 | es3: require('./parseInt'), 14 | nonew: require('./newSideEffects'), 15 | snakecase: require('./snake_case'), 16 | multivar: require('./multiVar'), 17 | plusplus: require('./updateExpression'), 18 | eqeqeq: require('./eqeqeq'), 19 | }, 20 | arefalse: { 21 | debug: require('./debugger'), 22 | sub: require('./dotNotation') 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/rules/initUndefined.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | VariableDeclarator: rmInitUndefined 3 | } 4 | 5 | function rmInitUndefined(node) { 6 | if (!node.init || 7 | node.init.type != 'Identifier' || 8 | node.init.name != 'undefined') { 9 | return node 10 | } 11 | 12 | return { 13 | type: 'VariableDeclarator', 14 | id: node.id, 15 | init: null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/rules/invalidConstructor.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NewExpression: rmBadConstructor 3 | } 4 | 5 | function isValidConstructor(node) { 6 | switch (node.name) { 7 | case 'Number': 8 | case 'String': 9 | case 'Boolean': 10 | case 'Math': 11 | case 'JSON': 12 | return false 13 | default: 14 | return true 15 | } 16 | } 17 | 18 | function rmBadConstructor(node) { 19 | if (node.callee.type != 'Identifier' || isValidConstructor(node.callee)) { 20 | return node 21 | } 22 | 23 | return { 24 | type: 'CallExpression', 25 | callee: node.callee, 26 | arguments: node.arguments 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/rules/isNaN.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | BinaryExpression: fixNaNComparisons 3 | } 4 | 5 | // Uses isNaN function rather than comparing to NaN. 6 | // 7 | // It's the same reason you shouldn't compare with undefined. 8 | // NaN can be redefined. Although comparing to NaN is faster 9 | // than using the isNaN function. 10 | 11 | var fu = require('fu') 12 | var comparisonOperators = /(=)==?/ 13 | 14 | function isNaNIdentifier(node) { 15 | return node.type == 'Identifier' && node.name == 'NaN' 16 | } 17 | 18 | function isNaNComparison(left, right) { 19 | return fu.any(isNaNIdentifier, [left, right]) 20 | } 21 | 22 | function getOtherNode(left, right) { 23 | return fu.filter(function (node) { 24 | return !isNaNIdentifier(node) 25 | }, [left, right]) 26 | } 27 | 28 | function fixNaNComparisons(node) { 29 | if (!comparisonOperators.test(node.operator) || 30 | !isNaNComparison(node.left, node.right)) { 31 | return node 32 | } 33 | 34 | var nanCall = { 35 | type: 'CallExpression', 36 | callee: { 37 | type: 'Identifier', 38 | name: 'isNaN' 39 | }, 40 | arguments: getOtherNode(node.left, node.right) 41 | } 42 | 43 | return node.operator[0] == '!' 44 | ? { type: 'UnaryExpression', operator: '!', argument: nanCall } 45 | : nanCall 46 | } 47 | -------------------------------------------------------------------------------- /lib/rules/multiVar.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | VariableDeclaration: multiVarDecl 3 | } 4 | 5 | var fu = require('fu'); 6 | 7 | function multiVarDecl(node, parent) { 8 | if (node.kind !== 'var' || node.declarations.length <= 1 || (parent && parent.type === 'ForStatement')) { 9 | return node 10 | } 11 | 12 | return fu.map(function(decl) { 13 | return { 14 | type: 'VariableDeclaration', 15 | kind: 'var', 16 | declarations: [decl] 17 | }; 18 | }, node.declarations) 19 | } 20 | -------------------------------------------------------------------------------- /lib/rules/newSideEffects.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NewExpression: noSideEffects 3 | } 4 | 5 | function noSideEffects(node, parent) { 6 | if (parent.type !== 'ExpressionStatement' || parent.expression.type === 'CallExpression') { 7 | return node 8 | } 9 | 10 | return { 11 | type: 'CallExpression', 12 | callee: node.callee, 13 | arguments: [] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/rules/parseInt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CallExpression: fixRadix 3 | } 4 | 5 | var fu = require('fu') 6 | 7 | function fixRadix(node) { 8 | if (node.callee.type != 'Identifier' || 9 | node.callee.name != 'parseInt' || 10 | node.arguments.length > 1) { 11 | return node 12 | } 13 | 14 | return { 15 | type: 'CallExpression', 16 | callee: node.callee, 17 | arguments: fu.concat(node.arguments, [{ 18 | type: 'Literal', 19 | value: 10 20 | }]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/rules/snake_case.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Identifier: toSnake 3 | } 4 | 5 | var regex = /[a-z]([A-Z])/g 6 | 7 | function toSnake(node, parent) { 8 | if (!regex.test(node.name)) { 9 | return node 10 | } 11 | 12 | if (parent) { 13 | if (parent.type == 'Property') { 14 | return node 15 | } 16 | 17 | if (parent.type == 'MemberExpression' && parent.property === node) { 18 | return node 19 | } 20 | } 21 | 22 | return { 23 | type: 'Identifier', 24 | name: node.name.replace(regex, function (i) { 25 | return i[0] + '_' + i[1].toLowerCase() 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/rules/updateExpression.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | UpdateExpression: rmPostfix 3 | } 4 | 5 | function rmPostfix(node) { 6 | if (node.operator != '--' && node.operator != '++') { 7 | return node 8 | } 9 | 10 | return { 11 | type: 'AssignmentExpression', 12 | operator: node.operator == '++' ? '+=' : '-=', 13 | left: node.argument, 14 | right: { 15 | type: 'Literal', 16 | value: 1 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/rules/useLiteral.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CallExpression: useLiteral, 3 | NewExpression: useLiteral 4 | } 5 | 6 | function canBeLiteral(node) { 7 | switch (node.name) { 8 | case 'Array': 9 | case 'Object': 10 | return true 11 | default: 12 | return false 13 | } 14 | } 15 | 16 | function getNode(node) { 17 | return node.name == 'Array' 18 | ? { type: 'ArrayExpression', elements: [] } 19 | : { type: 'ObjectExpression', properties: [] } 20 | } 21 | 22 | function useLiteral(node) { 23 | if (node.callee.type != 'Identifier' || 24 | !canBeLiteral(node.callee) || 25 | node.arguments.length > 0) { 26 | return node 27 | } 28 | 29 | return getNode(node.callee) 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixmyjs", 3 | "version": "1.0.3", 4 | "description": "Automatically fixes your JavaScript based on lint rules", 5 | "homepage": "http://fixmyjs.com", 6 | "license": "MIT", 7 | "author": "Josh Perez (http://github.com/goatslacker)", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/jshint/fixmyjs" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/jshint/fixmyjs/issues" 14 | }, 15 | "bin": { 16 | "fixmyjs": "./bin/fixmyjs" 17 | }, 18 | "keywords": [ 19 | "beautify", 20 | "hint", 21 | "jshint", 22 | "jslint", 23 | "lint" 24 | ], 25 | "main": "./lib/index.js", 26 | "dependencies": { 27 | "commander": "^2.3.0", 28 | "diff": "^1.0.7", 29 | "escodegen": "^1.4.1", 30 | "esprima": "^1.2.2", 31 | "fu": "^0.1.x", 32 | "jshint": "^2.5.6", 33 | "minimatch": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "coveralls": "^2.11.2", 37 | "ghooks": "^0.2.2", 38 | "istanbul": "^0.3.5", 39 | "testla": "^0.1.x" 40 | }, 41 | "preferGlobal": true, 42 | "scripts": { 43 | "lint": "jshint .", 44 | "coverage": "node ./node_modules/istanbul/lib/cli.js cover testla test", 45 | "test": "npm run lint && testla test" 46 | }, 47 | "files": [ 48 | "bin", 49 | "lib", 50 | "LICENSE", 51 | "README.md" 52 | ], 53 | "engines": { 54 | "node": "*" 55 | }, 56 | "config": { 57 | "ghooks": { 58 | "pre-push": "npm test" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/add-parentheses-to-constructors-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'adds parentheses to constructor': function () { 4 | var code = 'var foo = new Foo;' 5 | var result = 'var foo = new Foo();' 6 | assert.equal(f(code, {}), result) 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/array-literal-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'convert Array calls to literals': function () { 4 | var code = 'var a = Array();' 5 | assert.equal(f(code, {}), 'var a = [];') 6 | }, 7 | 8 | 'do not convert Array calls with an argument': function () { 9 | var code = 'var a = Array(5);' 10 | assert.equal(f(code, {}), 'var a = Array(5);') 11 | }, 12 | 13 | 'convert Array constructions': function () { 14 | var code = 'var a = new Array();' 15 | assert.equal(f(code, {}), 'var a = [];') 16 | }, 17 | 18 | 'do not convert Array constructions with an argument': function () { 19 | var code = 'var a = new Array(3);' 20 | assert.equal(f(code, {}), 'var a = new Array(3);') 21 | }, 22 | 23 | 'legacy Array literal': function () { 24 | var code = 'var foo = new Array();' 25 | assert.equal(l(code, {}), 'var foo = [];') 26 | }, 27 | 28 | 'legacy Array literal multiple statements': function () { 29 | var code = 'var foo = new Array(); var bar = new Array();' 30 | assert.equal(l(code, {}), 'var foo = []; var bar = [];') 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/asi-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'semicolons are automatically inserted': function () { 4 | var code = 'var f' 5 | var result = 'var f;' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/camelCase-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | var options = { camelcase: true } 3 | return { 4 | 'snake_case is converted to camelCase': function () { 5 | var code = 'snake_case;' 6 | var result = 'snakeCase;' 7 | assert.equal(f(code, options), result) 8 | }, 9 | 10 | 'leading and trailing underscores are not converted': function () { 11 | var code = '_private;' 12 | assert.equal(f(code, options), code) 13 | 14 | code = '__wtf;' 15 | assert.equal(f(code, options), code) 16 | 17 | code = 'yeah_;' 18 | assert.equal(f(code, options), code) 19 | 20 | code = 'triple___;' 21 | assert.equal(f(code, options), code) 22 | 23 | code = 'double__isGoodToo;' 24 | assert.equal(f(code, options), code) 25 | }, 26 | 27 | 'properties are not converted': function () { 28 | var code = 'var a = { snake_case: 1 };' 29 | assert.equal(f(code, options), code) 30 | }, 31 | 32 | 'member expressions are not converted': function () { 33 | var code = 'a.foo_bar();' 34 | assert.equal(f(code, options), code) 35 | }, 36 | 37 | 'member expressions where the object is snake is converted': function () { 38 | var code = 'new_collection[i] = callback();' 39 | var result = 'newCollection[i] = callback();' 40 | assert.equal(f(code, options), result) 41 | }, 42 | 43 | 'camelCase is not converted': function () { 44 | var code = 'camelCase;' 45 | assert.equal(f(code, options), code) 46 | }, 47 | 48 | 'function camel case gets converted': function () { 49 | var code = 'function test(fix_me) {}' 50 | var result = 'function test(fixMe) {\n}' 51 | var real = f(code, options) 52 | assert.equal(real, result) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/case-test.js: -------------------------------------------------------------------------------- 1 | // 'snake_case_not_tolerated', 2 | // '_leadingisok', 3 | // '__wtf', 4 | // 'yeah_', 5 | // 'yeah_n', 6 | // 'double__is_good_too', 7 | // 'whatAboutCamelCase', 8 | -------------------------------------------------------------------------------- /test/comments-must-stay-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'dont remove comments': function () { 4 | var code = 'var i = 0;\n//test\ni++;' 5 | var result = 'var i = 0;\n//test\ni++;' 6 | var real = f(code, {}); 7 | assert.equal(real, result) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/curly-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | var options = { curly: true } 3 | return { 4 | 'for statements get curly braces': function () { 5 | var code = 'for (;;) x;' 6 | var result = 'for (;;) {\n x;\n}' 7 | assert.equal(f(code, options), result) 8 | }, 9 | 10 | 'while statements with curly braces are left alone': function () { 11 | var code = 'while (true) {\n x;\n}' 12 | assert.equal(f(code, options), code) 13 | }, 14 | 15 | 'while statements get curlies': function () { 16 | var code = 'while (true) x;' 17 | var result = 'while (true) {\n x;\n}' 18 | assert.equal(f(code, options), result) 19 | }, 20 | 21 | 'if statements get curly braces': function () { 22 | var code = 'if (x) x; else x;' 23 | var result = 'if (x) {\n x;\n} else {\n x;\n}' 24 | assert.equal(f(code, options), result) 25 | }, 26 | 27 | 'if statements with no alternate get curlies': function () { 28 | var code = 'if (x) x;' 29 | var result = 'if (x) {\n x;\n}' 30 | assert.equal(f(code, options), result) 31 | }, 32 | 33 | 'leave elseif\'s be': function () { 34 | var code = 'if (x) x; else if (x || y) x;' 35 | var result = 'if (x) {\n x;\n} else if (x || y) {\n x;\n}' 36 | assert.equal(f(code, options), result) 37 | }, 38 | 39 | 'nested ifs': function () { 40 | var code = 'if (x) if (x || y) if (z) x;' 41 | var result = 'if (x) {\n if (x || y) {\n if (z) {\n x;\n }\n }\n}' 42 | assert.equal(f(code, options), result) 43 | }, 44 | 45 | 'nested else ifs': function () { 46 | var code = 'if (x) if (x || y) x; else if (z) x; else if(q) x; else x;' 47 | var result = 'if (x) {\n if (x || y) {\n x;\n } else if (z) {\n x;\n } else if (q) {\n x;\n } else {\n x;\n }\n}' 48 | assert.equal(f(code, options), result) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/debugger-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'remove debugger statements': function () { 4 | var code = 'debugger;' 5 | assert.equal(f(code, {}), '') 6 | assert.equal(l(code, {}), '') 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/delete-var-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'do not delete Identifiers': function () { 4 | var code = 'delete f;' 5 | var result = 'f = undefined;' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | }, 9 | 10 | 'do not fix delete for MemberExpression': function () { 11 | var code = 'delete f.u;' 12 | assert.equal(f(code, {}), code) 13 | assert.equal(l(code, {}), code) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/dependencies.js: -------------------------------------------------------------------------------- 1 | var fixmyjs = require('../') 2 | var jshint = require('jshint').JSHINT 3 | 4 | module.exports = { 5 | f: fixmyjs.fix, 6 | l: function (code, options) { 7 | jshint(code, options) 8 | return fixmyjs(jshint.data(), code, options).run() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/dot-notation-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'square bracket notation is converted to dot notation': function () { 4 | var code = 'a["b"];' 5 | var result = 'a.b;' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | }, 9 | 10 | 'chained computed member expression to dot notation': function () { 11 | var code = 'a["b"]["c"];' 12 | var result = 'a.b.c;' 13 | assert.equal(f(code, {}), result) 14 | assert.equal(l(code, {}), result) 15 | }, 16 | 17 | 'computed member expression for reserved words': function () { 18 | var code = 'a[\'for\'];' 19 | assert.equal(f(code, {}), code) 20 | assert.equal(l(code, {}), code) 21 | }, 22 | 23 | 'do not convert square bracket notation if sub set to true': function () { 24 | var code = 'a["b"];' 25 | var result = 'a[\'b\'];' 26 | assert.equal(f(code, { sub: true }), result) 27 | }, 28 | 29 | 'do not convert chained computed member expression if set set to true': function () { 30 | var code = 'a["b"]["c"];' 31 | var result = 'a[\'b\'][\'c\'];' 32 | assert.equal(f(code, { sub: true }), result) 33 | }, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/empty-statements-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | return { 3 | 'sole empty statements are removed': function () { 4 | var code = 'var a = 1;;' 5 | var result = 'var a = 1;' 6 | assert.equal(f(code, {}), result) 7 | }, 8 | 9 | 'empty statements are removed within functions': function () { 10 | var code = 'function x() { var a = 1;; }' 11 | var result = 'function x() {\n var a = 1;\n}' 12 | assert.equal(f(code, {}), result) 13 | }, 14 | 15 | 'empty statements dont fatal the rewriter': function () { 16 | var code = 'for (;;);' 17 | var result = 'for (;;);' 18 | assert.equal(f(code, {}), result) 19 | }, 20 | 21 | 'empty statements work for if': function () { 22 | var code = 'if (true);' 23 | var result = 'if (true);' 24 | assert.equal(f(code, {}), result) 25 | }, 26 | 27 | 'empty statements work for while': function () { 28 | var code = 'while (true);' 29 | var result = 'while (true);' 30 | assert.equal(f(code, {}), result) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/eqeqeq-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | var options = { eqeqeq: true, _silent: true } 3 | return { 4 | '== is converted to ===': function () { 5 | var code = 'a == null;' 6 | var result = 'a === null;' 7 | assert.equal(f(code, options), result) 8 | }, 9 | 10 | '!= is converted to !==': function () { 11 | var code = '1 != 2;' 12 | var result = '1 !== 2;' 13 | assert.equal(f(code, options), result) 14 | }, 15 | 16 | '=== is not converted': function () { 17 | var code = 'g === undefined;' 18 | assert.equal(f(code, options), code) 19 | }, 20 | 21 | '> is not converted': function () { 22 | var code = '4 > 3;' 23 | assert.equal(f(code, options), code) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/immed-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'dogballs are placed inside the parentheses': function () { 4 | var code = '(function () { })();' 5 | var result = '(function () {\n}());' 6 | assert.equal(f(code, {}), result) 7 | }, 8 | 9 | 'legacy accepts dogballs': function () { 10 | var code = '(function () { })();' 11 | assert.equal(l(code, {}), code) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/init-undefined-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'removes var initialization to undefined': function () { 4 | var code = 'var a = undefined;' 5 | var result = 'var a;' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/invalid-construction-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'Number constructions': function () { 4 | var code = 'var a = new Number(3);' 5 | var result = 'var a = Number(3);' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | }, 9 | 10 | 'String constructions': function () { 11 | var code = 'var a = new String(\'foo\');' 12 | var result = 'var a = String(\'foo\');' 13 | assert.equal(f(code, {}), result) 14 | assert.equal(l(code, {}), result) 15 | }, 16 | 17 | 'JSON constructions': function () { 18 | var code = 'var a = new JSON({});' 19 | var result = 'var a = JSON({});' 20 | assert.equal(f(code, {}), result) 21 | assert.equal(l(code, {}), result) 22 | }, 23 | 24 | 'Math constructions': function () { 25 | var code = 'var a = new Math(2 + 2);' 26 | var result = 'var a = Math(2 + 2);' 27 | assert.equal(f(code, {}), result) 28 | assert.equal(l(code, {}), result) 29 | }, 30 | 31 | 'Boolean constructions': function () { 32 | var code = 'var d = c(new Boolean(false));' 33 | var result = 'var d = c(Boolean(false));' 34 | assert.equal(f(code, {}), result) 35 | assert.equal(l(code, {}), result) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/invoke-constructor-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'constructors are invocations': function () { 4 | var code = 'var foo = new Foo;' 5 | var result = 'var foo = new Foo();' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | }, 9 | 10 | 'constructors have invocation multiple statements': function () { 11 | var code = 'var foo = new Foo; var baz = new Baz(); var bar = new Bar;' 12 | assert.equal(l(code, {}), 'var foo = new Foo(); var baz = new Baz(); ' + 13 | 'var bar = new Bar();') 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/isNaN-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'eq comparisons to NaN are replaced with isNaN function': function () { 4 | var code = 'a == NaN;' 5 | var result = 'isNaN(a);' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | }, 9 | 10 | 'neq comparisons to NaN are left untouched': function () { 11 | var code = 'a != NaN;' 12 | assert.equal(f(code, {}), 'a != NaN;') 13 | assert.equal(l(code, {}), '!isNaN(a);') 14 | }, 15 | 16 | 'comparisons to NaN with literals': function () { 17 | var code = '4 == NaN;' 18 | var result = 'isNaN(4);' 19 | assert.equal(f(code, {}), result) 20 | }, 21 | 22 | 'multiline NaN comparisons are fixed': function () { 23 | var code = [ 24 | '(foo || bar(2))', 25 | '== NaN;' 26 | ].join('\n') 27 | var result = 'isNaN(foo || bar(2));' 28 | assert.equal(f(code, {}), result) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/leading-decimal-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'fix for leading decimals': function () { 4 | var code = 'var a = .25;' 5 | var result = 'var a = 0.25;' 6 | assert.equal(f(code, {}), result) 7 | }, 8 | 9 | 'leading decimals on member expressions': function () { 10 | var code = 'Foo.bar = .25;' 11 | var result = 'Foo.bar = 0.25;' 12 | assert.equal(f(code, {}), result) 13 | }, 14 | 15 | 'legacy does not fix leading decimals': function () { 16 | var code = 'var a = .25;' 17 | var result = 'var a = .25;' 18 | assert.equal(l(code, {}), result) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/multivar-for-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | var options = { multivar: true } 3 | return { 4 | 'should not split var declarations in loops': function () { 5 | var code = 'for (var i = 0, k = 1;;) {\n}' 6 | var real = f(code, options) 7 | assert.equal(real, code) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/multivar-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | var options = { multivar: true } 3 | return { 4 | 'splits multivar declarations into multiple statements': function () { 5 | var code = 'var a, b;' 6 | var result = 'var a;\nvar b;' 7 | var real = f(code, options) 8 | assert.equal(real, result) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/new-as-side-effects-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | var options = { nonew: true } 3 | return { 4 | 'no new as side effects': function () { 5 | var code = 'new Foo();' 6 | var result = 'Foo();' 7 | assert.equal(f(code, options), result) 8 | assert.equal(l(code, options), result) 9 | }, 10 | 11 | 'missing parens plus side effects': function () { 12 | var code = 'new Foo;' 13 | var result = 'Foo();' 14 | assert.equal(f(code, options), result) 15 | }, 16 | 17 | 'new as function parameter': function () { 18 | var code = 'c(new Foo());' 19 | var result = 'c(new Foo());' 20 | assert.equal(f(code, options), result) 21 | }, 22 | 23 | 'new as function parameter without parens': function () { 24 | var code = 'c(new Foo);' 25 | var result = 'c(new Foo());' 26 | assert.equal(f(code, options), result) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/object-literal-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'convert Object calls to literals': function () { 4 | var code = 'var a = Object();' 5 | assert.equal(f(code, {}), 'var a = {};') 6 | }, 7 | 8 | 'do not convert Object calls with an argument': function () { 9 | var code = 'var a = Object(null);' 10 | assert.equal(f(code, {}), 'var a = Object(null);') 11 | }, 12 | 13 | 'convert Object constructions': function () { 14 | var code = 'var a = new Object();' 15 | assert.equal(f(code, {}), 'var a = {};') 16 | }, 17 | 18 | 'do not convert Object constructions with an argument': function () { 19 | var code = 'var a = new Object(null);' 20 | assert.equal(f(code, {}), 'var a = new Object(null);') 21 | }, 22 | 23 | 'legacy Object literal': function () { 24 | var code = 'var foo = new Object();' 25 | assert.equal(l(code, {}), 'var foo = {};') 26 | }, 27 | 28 | 'legacy Ojbect literal multiple statements': function () { 29 | var code = 'var foo = new Object(); var bar = new Object();' 30 | assert.equal(l(code, {}), 'var foo = {}; var bar = {};') 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/parseInt-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | var options = { es3: true } 3 | return { 4 | 'parseInt gets the radix': function () { 5 | var code = 'parseInt(23820);' 6 | var result = 'parseInt(23820, 10);' 7 | assert.equal(f(code, options), result) 8 | assert.equal(l(code, options), result) 9 | }, 10 | 11 | 'parseInt works regardless of parentheses nesting': function () { 12 | var code = 'parseInt(someFunction(1));' 13 | var result = 'parseInt(someFunction(1), 10);' 14 | assert.equal(f(code, options), result) 15 | assert.equal(l(code, options), code) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/shebang-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | return { 3 | 'remove shebang from code': function () { 4 | var code = [ 5 | '#!/usr/bin/env node', 6 | 'module.exports = 2;' 7 | ].join('\n') 8 | assert.equal(f(code, {}), code) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/snake_case-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | var options = { snakecase: true } 3 | return { 4 | 'camelCase is converted to snake_case': function () { 5 | var code = 'snakeCase;' 6 | var result = 'snake_case;' 7 | assert.equal(f(code, options), result) 8 | }, 9 | 10 | 'properties are not converted': function () { 11 | var code = 'var a = { camelCase: 1 };' 12 | assert.equal(f(code, options), code) 13 | }, 14 | 15 | 'member expressions are not converted': function () { 16 | var code = 'a.fooBar();' 17 | assert.equal(f(code, options), code) 18 | }, 19 | 20 | 'snake_case is not converted': function () { 21 | var code = 'snake_case;' 22 | assert.equal(f(code, options), code) 23 | }, 24 | 25 | 'function camel case gets converted': function () { 26 | var code = 'function test(fixMe) {}' 27 | var result = 'function test(fix_me) {\n}' 28 | var real = f(code, options) 29 | assert.equal(real, result) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/trailing-commas-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | return { 3 | 'trailing commas are removed': function () { 4 | var code = 'foo([1, 2, 3,]);' 5 | var result = 'foo([\n 1,\n 2,\n 3\n]);' 6 | assert.equal(f(code, { indent: 2 }), result) 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/trailing-decimal-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'fix for trailing decimals': function () { 4 | var code = 'var a = 2.;' 5 | var result = 'var a = 2;' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/unnecessary-semicolon-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | return { 3 | 'unnecessary semicolons are removed': function () { 4 | var code = 'var a = 1;;' 5 | var result = 'var a = 1;' 6 | assert.equal(f(code, {}), result) 7 | assert.equal(l(code, {}), result) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/update-expression-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, l, assert) { 2 | var options = { plusplus: true } 3 | 4 | return { 5 | 'plusplus is changed to += 1': function () { 6 | var code = 'a++;' 7 | var result = 'a += 1;' 8 | assert.equal(f(code, options), result) 9 | }, 10 | 11 | 'minusminus is changed to -= 1': function () { 12 | var code = 'a--;' 13 | var result = 'a -= 1;' 14 | assert.equal(f(code, options), result) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/validate-rules-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (f, assert) { 2 | return { 3 | 'cant contain both camelCase and snake_case': function () { 4 | assert.throws(function () { 5 | f('var f', { 6 | camelcase: true, 7 | snakecase: true 8 | }) 9 | }, Error) 10 | } 11 | } 12 | } 13 | --------------------------------------------------------------------------------