Comparing version 1.11.3 to 1.12.0
@@ -38,12 +38,31 @@ var vowFs = require('vow-fs'); | ||
Checker.prototype.checkFile = function(path) { | ||
if (!this._configuration.isFileExcluded(path)) { | ||
return vowFs.read(path, 'utf8').then(function(data) { | ||
return this.checkString(data, path); | ||
}, this); | ||
if (this._configuration.isFileExcluded(path)) { | ||
return Vow.resolve(null); | ||
} | ||
return Vow.resolve(null); | ||
return vowFs.read(path, 'utf8').then(function(data) { | ||
return this.checkString(data, path); | ||
}, this); | ||
}; | ||
/** | ||
* Fixes single file. | ||
* | ||
* @param {String} path | ||
* @returns {Promise * Errors} | ||
*/ | ||
Checker.prototype.fixFile = function(path) { | ||
if (this._configuration.isFileExcluded(path)) { | ||
return Vow.resolve(null); | ||
} | ||
return vowFs.read(path, 'utf8').then(function(data) { | ||
var result = this.fixString(data, path); | ||
return vowFs.write(path, result.output).then(function() { | ||
return result.errors; | ||
}); | ||
}, this); | ||
}; | ||
/** | ||
* Checks directory recursively. | ||
@@ -55,2 +74,33 @@ * | ||
Checker.prototype.checkDirectory = function(path) { | ||
return this._processDirectory(path, this.checkFile.bind(this)); | ||
}; | ||
/** | ||
* Checks directory or file. | ||
* | ||
* @param {String} path | ||
* @returns {Promise * Error[]} | ||
*/ | ||
Checker.prototype.checkPath = function(path) { | ||
return this._processPath(path, this.checkFile.bind(this)); | ||
}; | ||
/** | ||
* Fixes directory or file. | ||
* | ||
* @param {String} path | ||
* @returns {Promise * Error[]} | ||
*/ | ||
Checker.prototype.fixPath = function(path) { | ||
return this._processPath(path, this.fixFile.bind(this)); | ||
}; | ||
/** | ||
* Processes directory recursively. | ||
* | ||
* @param {String} path | ||
* @param {Function} fileHandler | ||
* @returns {Promise * Error[]} | ||
*/ | ||
Checker.prototype._processDirectory = function(path, fileHandler) { | ||
return vowFs.listDir(path).then(function(filenames) { | ||
@@ -66,3 +116,3 @@ return Vow.all(filenames.map(function(filename) { | ||
if (stat.isDirectory()) { | ||
return this.checkDirectory(fullname); | ||
return this._processDirectory(fullname, fileHandler); | ||
} | ||
@@ -74,3 +124,3 @@ | ||
return Vow.when(this.checkFile(fullname)).then(function(errors) { | ||
return Vow.when(fileHandler(fullname)).then(function(errors) { | ||
if (errors) { | ||
@@ -90,8 +140,9 @@ return errors; | ||
/** | ||
* Checks directory or file. | ||
* Processes directory or file. | ||
* | ||
* @param {String} path | ||
* @returns {Error[]} | ||
* @param {Function} fileHandler | ||
* @returns {Promise * Error[]} | ||
*/ | ||
Checker.prototype.checkPath = function(path) { | ||
Checker.prototype._processPath = function(path, fileHandler) { | ||
path = path.replace(/\/$/, ''); | ||
@@ -107,6 +158,6 @@ var _this = this; | ||
if (stat.isDirectory()) { | ||
return _this.checkDirectory(path); | ||
return _this._processDirectory(path, fileHandler); | ||
} | ||
return _this.checkFile(path).then(function(errors) { | ||
return fileHandler(path).then(function(errors) { | ||
if (errors) { | ||
@@ -128,5 +179,22 @@ return [errors]; | ||
Checker.prototype.checkStdin = function() { | ||
return this._processStdin(this.checkString.bind(this)); | ||
}; | ||
/** | ||
* Fixes stdin input | ||
* | ||
* @returns {Promise} | ||
*/ | ||
Checker.prototype.fixStdin = function() { | ||
return this._processStdin(this.fixString.bind(this)); | ||
}; | ||
/** | ||
* | ||
* @param {Function} stdinHandler | ||
* @returns {Promise} | ||
*/ | ||
Checker.prototype._processStdin = function(stdinHandler) { | ||
var stdInput = []; | ||
var deferred = Vow.defer(); | ||
var _this = this; | ||
@@ -140,5 +208,3 @@ process.stdin.setEncoding('utf8'); | ||
process.stdin.on('end', function() { | ||
var errors = _this.checkString(stdInput.join('')); | ||
deferred.resolve(errors); | ||
deferred.resolve(stdinHandler(stdInput.join(''))); | ||
}); | ||
@@ -145,0 +211,0 @@ |
@@ -9,3 +9,3 @@ /** | ||
var stripJSONComments = require('strip-json-comments'); | ||
var supportsColor = require('supports-color'); | ||
var supportsColor = require('chalk').supportsColor; | ||
var glob = require('glob'); | ||
@@ -24,5 +24,5 @@ | ||
options = Object.create(options || {}); | ||
options = Object.create(options); | ||
options.maxDepth = 1; | ||
options.cwd = path.resolve(options.cwd || '.'); | ||
options.cwd = path.resolve(options.cwd); | ||
@@ -34,3 +34,3 @@ do { | ||
if (configPath) { | ||
return !fn || fn(path.join(options.cwd, configPath)); | ||
return fn(path.join(options.cwd, configPath)); | ||
} | ||
@@ -92,3 +92,3 @@ })[0]; | ||
* @param {String} [cwd = process.cwd()] - directory path which will be joined with config argument | ||
* @return {Object} | ||
* @return {Object|undefined} | ||
*/ | ||
@@ -168,5 +168,5 @@ exports.load = function(config, cwd) { | ||
return { | ||
path: writerPath, | ||
writer: writer | ||
path: writerPath, | ||
writer: writer | ||
}; | ||
}; |
@@ -117,2 +117,11 @@ /** | ||
if (program.fix) { | ||
return { | ||
promise: checker.fixStdin().then(function(result) { | ||
process.stdout.write(result.output); | ||
}), | ||
checker: checker | ||
}; | ||
} | ||
checkerPromise = checker.checkStdin().then(function(errors) { | ||
@@ -125,3 +134,4 @@ return [errors]; | ||
if (args.length) { | ||
checkerPromise = Vow.all(args.map(checker.checkPath, checker)).then(function(results) { | ||
var method = program.fix ? checker.fixPath : checker.checkPath; | ||
checkerPromise = Vow.all(args.map(method, checker)).then(function(results) { | ||
return [].concat.apply([], results); | ||
@@ -128,0 +138,0 @@ }); |
@@ -567,2 +567,3 @@ var assert = require('assert'); | ||
this.registerRule(require('../rules/disallow-multiple-line-strings')); | ||
this.registerRule(require('../rules/disallow-multiple-spaces')); | ||
this.registerRule(require('../rules/validate-line-breaks')); | ||
@@ -597,2 +598,5 @@ this.registerRule(require('../rules/validate-quote-marks')); | ||
this.registerRule(require('../rules/disallow-padding-newlines-after-blocks')); | ||
this.registerRule(require('../rules/require-padding-newlines-after-blocks')); | ||
this.registerRule(require('../rules/disallow-padding-newlines-in-blocks')); | ||
@@ -608,5 +612,10 @@ this.registerRule(require('../rules/require-padding-newlines-in-blocks')); | ||
this.registerRule(require('../rules/disallow-padding-newlines-before-line-comments')); | ||
this.registerRule(require('../rules/require-padding-newlines-before-line-comments')); | ||
this.registerRule(require('../rules/disallow-trailing-comma')); | ||
this.registerRule(require('../rules/require-trailing-comma')); | ||
this.registerRule(require('../rules/require-dollar-before-jquery-assignment')); | ||
this.registerRule(require('../rules/disallow-comma-before-line-break')); | ||
@@ -670,3 +679,5 @@ this.registerRule(require('../rules/require-comma-before-line-break')); | ||
this.registerRule(require('../rules/require-line-break-after-variable-assignment')); | ||
this.registerRule(require('../rules/require-padding-newline-after-variable-declaration')); | ||
this.registerRule(require('../rules/require-semicolons')); | ||
this.registerRule(require('../rules/disallow-semicolons')); | ||
@@ -676,3 +687,6 @@ | ||
this.registerRule(require('../rules/disallow-spaces-in-for-statement')); | ||
this.registerRule(require('../rules/disallow-keywords-in-comments')); | ||
this.registerRule(require('../rules/disallow-identifier-names')); | ||
}; | ||
@@ -702,5 +716,11 @@ | ||
// https://github.com/felixge/node-style-guide#nodejs-style-guide | ||
this.registerPreset('node-style-guide', require('../../presets/node-style-guide.json')); | ||
// https://www.mediawiki.org/wiki/Manual:Coding_conventions/JavaScript | ||
this.registerPreset('wikimedia', require('../../presets/wikimedia.json')); | ||
// https://make.wordpress.org/core/handbook/coding-standards/javascript/ | ||
this.registerPreset('wordpress', require('../../presets/wordpress.json')); | ||
// https://github.com/yandex/codestyle/blob/master/javascript.md | ||
@@ -707,0 +727,0 @@ this.registerPreset('yandex', require('../../presets/yandex.json')); |
@@ -6,3 +6,3 @@ var fs = require('fs'); | ||
var prompt = require('prompt'); | ||
var colors = require('colors'); | ||
var chalk = require('chalk'); | ||
var assign = require('lodash.assign'); | ||
@@ -25,3 +25,3 @@ | ||
{ | ||
name: colors.green('Please choose a preset number:'), | ||
name: chalk.green('Please choose a preset number:'), | ||
require: true, | ||
@@ -78,6 +78,5 @@ pattern: /\d+/ | ||
var errorList = statsForPresets[presetIndex].errors; | ||
errorList = getUniqueErrorNames(errorList); | ||
var errorStats = getErrorsByRuleName(statsForPresets[presetIndex].errors); | ||
var violatedRuleCount = errorList.length; | ||
var violatedRuleCount = Object.keys(errorStats).length; | ||
@@ -88,6 +87,6 @@ if (!violatedRuleCount) { return this._config; } | ||
var errorPrompts = generateRuleHandlingPrompts(errorList); | ||
var errorPrompts = generateRuleHandlingPrompts(errorStats); | ||
return this._getUserViolationChoices(errorPrompts) | ||
.then(this._handleViolatedRules.bind(this, errorPrompts, errorList)) | ||
.then(this._handleViolatedRules.bind(this, errorPrompts)) | ||
.then(function() { | ||
@@ -136,3 +135,3 @@ return this._config; | ||
statsForPresets.forEach(function(presetStats, idx) { | ||
table.push([idx + 1, presetStats.name, presetStats.sum]); | ||
table.push([idx + 1, presetStats.name, presetStats.sum, getUniqueErrorNames(presetStats.errors).length]); | ||
}); | ||
@@ -174,9 +173,8 @@ | ||
*/ | ||
Generator.prototype._handleViolatedRules = function(errorPrompts, errorList, choices) { | ||
errorPrompts.forEach(function(errorPrompt, idx) { | ||
var associatedRuleName = errorList[idx]; | ||
Generator.prototype._handleViolatedRules = function(errorPrompts, choices) { | ||
errorPrompts.forEach(function(errorPrompt) { | ||
var userChoice = choices[errorPrompt.name]; | ||
if (userChoice.toLowerCase() === 'e') { | ||
this._config[associatedRuleName] = null; | ||
this._config[errorPrompt.associatedRuleName] = null; | ||
} | ||
@@ -212,9 +210,22 @@ }, this); | ||
* @private | ||
* @param {String[]} violatedRuleNames | ||
* @param {Object} errors | ||
* @return {Object[]} | ||
*/ | ||
function generateRuleHandlingPrompts(violatedRuleNames) { | ||
function generateRuleHandlingPrompts(errors) { | ||
// Generate list of rule names, sorted by violation count (descending) | ||
var violatedRuleNames = Object.keys(errors); | ||
violatedRuleNames.sort(function(a, b) { | ||
return errors[b].violations - errors[a].violations; | ||
}); | ||
return violatedRuleNames.map(function(ruleName) { | ||
var violationCount = errors[ruleName].violations; | ||
var fileCount = Object.keys(errors[ruleName].files).length; | ||
var prompt = assign({}, prompts[1]); | ||
prompt.name = colors.green(ruleName) + ': ' + prompt.name; | ||
prompt.associatedRuleName = ruleName; | ||
prompt.name = chalk.green(ruleName) + | ||
' (' + violationCount + ' violation' + (violationCount > 1 ? 's' : '') + | ||
' in ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '):\n ' + | ||
prompt.name; | ||
return prompt; | ||
@@ -226,2 +237,23 @@ }); | ||
* @private | ||
* @param {Object[]} errorList | ||
* @return {Object} | ||
*/ | ||
function getErrorsByRuleName(errorsList) { | ||
var errors = {}; | ||
errorsList.forEach(function(error) { | ||
var rulename = error.rule; | ||
errors[rulename] = errors[rulename] || { | ||
files: {}, | ||
violations: 0 | ||
}; | ||
errors[rulename].violations += 1; | ||
errors[rulename].files[error.filename] = true; | ||
}); | ||
return errors; | ||
} | ||
/** | ||
* @private | ||
* @param {Object[]} errorsList | ||
@@ -258,3 +290,3 @@ * @return {String[]} | ||
}, | ||
head: ['', 'Preset', '#Errors'] | ||
head: ['', 'Preset', '#Errors', '#Rules'] | ||
}); | ||
@@ -261,0 +293,0 @@ } |
var assert = require('assert'); | ||
var colors = require('colors'); | ||
var chalk = require('chalk'); | ||
var TokenAssert = require('./token-assert'); | ||
@@ -69,3 +69,4 @@ | ||
line: errorInfo.line, | ||
column: errorInfo.column | ||
column: errorInfo.column, | ||
fixed: errorInfo.fixed | ||
}); | ||
@@ -157,3 +158,3 @@ }, | ||
/** | ||
/** | ||
* Sets the current rule so that errors are aware | ||
@@ -179,5 +180,5 @@ * of which rule triggered them. | ||
function formatErrorMessage(message, filename, colorize) { | ||
return (colorize ? colors.bold(message) : message) + | ||
return (colorize ? chalk.bold(message) : message) + | ||
' at ' + | ||
(colorize ? colors.green(filename) : filename) + ' :'; | ||
(colorize ? chalk.green(filename) : filename) + ' :'; | ||
} | ||
@@ -214,3 +215,3 @@ | ||
var lineNumber = prependSpaces((n + 1).toString(), 5) + ' |'; | ||
return ' ' + (colorize ? colors.grey(lineNumber) : lineNumber) + line; | ||
return ' ' + (colorize ? chalk.grey(lineNumber) : lineNumber) + line; | ||
} | ||
@@ -228,5 +229,5 @@ | ||
var res = (new Array(column + 9)).join('-') + '^'; | ||
return colorize ? colors.grey(res) : res; | ||
return colorize ? chalk.grey(res) : res; | ||
} | ||
module.exports = Errors; |
@@ -21,3 +21,3 @@ var treeIterator = require('./tree-iterator'); | ||
this._source = source; | ||
this._tree = tree || {tokens: []}; | ||
this._tree = tree || {tokens: [], comments: []}; | ||
@@ -27,40 +27,19 @@ this._es3 = options.es3 || false; | ||
this._lineBreaks = null; | ||
this._lines = source.split(/\r\n|\r|\n/); | ||
this._tokenRangeStartIndex = null; | ||
this._tokenRangeEndIndex = null; | ||
var index = this._index = {}; | ||
var _this = this; | ||
this._buildTokenIndex(); | ||
this._tree.tokens = this._fixEs6Tokens(this._tree.tokens); | ||
this._tokens = this._buildTokenList(this._tree.tokens, this._tree.comments); | ||
this._addEOFToken(); | ||
this._applyWhitespaceData(this._tokens, source); | ||
this.iterate(function(node, parentNode, parentCollection) { | ||
var type = node.type; | ||
var tokenIndexes = this._buildTokenIndex(this._tokens); | ||
this._tokenRangeStartIndex = tokenIndexes.tokenRangeStartIndex; | ||
this._tokenRangeEndIndex = tokenIndexes.tokenRangeEndIndex; | ||
this._tokensByLineIndex = tokenIndexes.tokensByLineIndex; | ||
node.parentNode = parentNode; | ||
node.parentCollection = parentCollection; | ||
(index[type] || (index[type] = [])).push(node); | ||
this._index = this._buildNodeIndex(); | ||
this._fixEsprimaIdentifiers(); | ||
// Temporary fix (i hope) for esprima tokenizer | ||
// (https://code.google.com/p/esprima/issues/detail?id=481) | ||
// Fixes #83, #180 | ||
switch (type) { | ||
case 'Property': | ||
convertKeywordToIdentifierIfRequired(node.key); | ||
break; | ||
case 'MemberExpression': | ||
convertKeywordToIdentifierIfRequired(node.property); | ||
break; | ||
} | ||
}); | ||
this._buildDisabledRuleIndex(); | ||
// Part of temporary esprima fix. | ||
function convertKeywordToIdentifierIfRequired(node) { | ||
var tokenPos = _this.getTokenPosByRangeStart(node.range[0]); | ||
var token = _this._tree.tokens[tokenPos]; | ||
if (token.type === 'Keyword') { | ||
token.type = 'Identifier'; | ||
} | ||
} | ||
}; | ||
@@ -70,3 +49,28 @@ | ||
/** | ||
* Returns the first line break character encountered in the file. | ||
* Assumes LF if the file is only one line. | ||
* | ||
* @returns {String} | ||
*/ | ||
getLineBreakStyle: function() { | ||
var lineBreaks = this.getLineBreaks(); | ||
return lineBreaks.length ? lineBreaks[0] : '\n'; | ||
}, | ||
/** | ||
* Returns all line break characters from the file. | ||
* | ||
* @returns {String[]} | ||
*/ | ||
getLineBreaks: function() { | ||
if (this._lineBreaks === null) { | ||
this._lineBreaks = this._source.match(/\r\n|\r|\n/g) || []; | ||
} | ||
return this._lineBreaks; | ||
}, | ||
/** | ||
* Builds an index of disabled rules by starting line for error suppression. | ||
* | ||
* @private | ||
*/ | ||
@@ -76,3 +80,3 @@ _buildDisabledRuleIndex: function() { | ||
var comments = this.getComments() || []; | ||
var comments = this.getComments(); | ||
var commentRe = /(jscs\s*:\s*(en|dis)able)(.*)/; | ||
@@ -123,2 +127,3 @@ | ||
* @param {Number} line the line the comment appears on | ||
* @private | ||
*/ | ||
@@ -145,74 +150,35 @@ _addToDisabledRuleIndex: function(enabled, rulesStr, line) { | ||
* Builds token index by starting pos for futher navigation. | ||
* | ||
* @param {Object[]} tokens | ||
* @returns {{tokenRangeStartIndex: {}, tokenRangeEndIndex: {}}} | ||
* @private | ||
*/ | ||
_buildTokenIndex: function() { | ||
// Temporary fix (i hope) for esprima tokenizer, which results | ||
// in duplicate tokens on `export default function() {}` | ||
// (https://code.google.com/p/esprima/issues/detail?id=631) | ||
if (this.getDialect() === 'es6') { | ||
var tokenHash = {}; | ||
this._tree.tokens = this._tree.tokens.filter(function(token) { | ||
var hashKey = token.range[0] + '_' + token.range[1]; | ||
var isDuplicate = tokenHash[hashKey]; | ||
tokenHash[hashKey] = true; | ||
return !isDuplicate; | ||
}); | ||
} | ||
var tokens = this._tree.tokens; | ||
_buildTokenIndex: function(tokens) { | ||
var tokenRangeStartIndex = {}; | ||
var tokenRangeEndIndex = {}; | ||
var tokensByLineIndex = {}; | ||
for (var i = 0, l = tokens.length; i < l; i++) { | ||
tokenRangeStartIndex[tokens[i].range[0]] = i; | ||
tokenRangeEndIndex[tokens[i].range[1]] = i; | ||
tokens[i]._tokenIndex = i; | ||
} | ||
this._tokenRangeStartIndex = tokenRangeStartIndex; | ||
this._tokenRangeEndIndex = tokenRangeEndIndex; | ||
}, | ||
/** | ||
* Builds comments index by starting pos for futher navigation. | ||
*/ | ||
_buildCommentIndex: function() { | ||
var comments = this.getComments(); | ||
var tokens = this.getTokens(); | ||
var tokenIndex = 0; | ||
var tokensLength = tokens.length; | ||
var tokenCommentsBeforeIndex = this._tokenCommentsBeforeIndex = {}; | ||
var tokenCommentsAfterIndex = this._tokenCommentsAfterIndex = {}; | ||
var partialComments = []; | ||
for (var i = 0, l = comments.length; i < l && comments[i].range[1] <= tokens[0].range[0]; i++) { | ||
partialComments.push(comments[i]); | ||
} | ||
if (partialComments.length) { | ||
tokenCommentsBeforeIndex[0] = partialComments; | ||
partialComments = []; | ||
} | ||
for (; i < l; i++) { | ||
var startPos = comments[i].range[0]; | ||
if (partialComments.length) { | ||
tokenCommentsAfterIndex[tokenIndex] = partialComments; | ||
tokenCommentsBeforeIndex[tokenIndex + 1] = partialComments; | ||
partialComments = []; | ||
var token = tokens[i]; | ||
// tokens by range | ||
tokenRangeStartIndex[token.range[0]] = i; | ||
tokenRangeEndIndex[token.range[1]] = i; | ||
// tokens by line | ||
var lineNumber = token.loc.start.line; | ||
if (!tokensByLineIndex[lineNumber]) { | ||
tokensByLineIndex[lineNumber] = []; | ||
} | ||
while (tokenIndex + 1 < tokensLength && tokens[tokenIndex + 1].range[1] <= startPos) { | ||
tokenIndex++; | ||
} | ||
partialComments.push(comments[i]); | ||
tokensByLineIndex[lineNumber].push(token); | ||
token._tokenIndex = i; | ||
} | ||
if (partialComments.length) { | ||
tokenCommentsAfterIndex[tokenIndex] = partialComments; | ||
tokenCommentsBeforeIndex[tokenIndex + 1] = partialComments; | ||
} | ||
return { | ||
tokenRangeStartIndex: tokenRangeStartIndex, | ||
tokenRangeEndIndex: tokenRangeEndIndex, | ||
tokensByLineIndex: tokensByLineIndex | ||
}; | ||
}, | ||
/** | ||
* Returns token position using range start from the index. | ||
* | ||
* @returns {Object} | ||
*/ | ||
getTokenPosByRangeStart: function(start) { | ||
return this._tokenRangeStartIndex[start]; | ||
}, | ||
/** | ||
* Returns token using range start from the index. | ||
@@ -224,4 +190,5 @@ * | ||
var tokenIndex = this._tokenRangeStartIndex[start]; | ||
return tokenIndex === undefined ? undefined : this._tree.tokens[tokenIndex]; | ||
return tokenIndex === undefined ? undefined : this._tokens[tokenIndex]; | ||
}, | ||
/** | ||
@@ -234,4 +201,5 @@ * Returns token using range end from the index. | ||
var tokenIndex = this._tokenRangeEndIndex[end]; | ||
return tokenIndex === undefined ? undefined : this._tree.tokens[tokenIndex]; | ||
return tokenIndex === undefined ? undefined : this._tokens[tokenIndex]; | ||
}, | ||
/** | ||
@@ -246,2 +214,3 @@ * Returns the first token for the node from the AST. | ||
}, | ||
/** | ||
@@ -256,12 +225,48 @@ * Returns the last token for the node from the AST. | ||
}, | ||
/** | ||
* Returns the first token for the file. | ||
* | ||
* @returns {Object} | ||
*/ | ||
getFirstToken: function() { | ||
return this._tokens[0]; | ||
}, | ||
/** | ||
* Returns the last token for the file. | ||
* | ||
* @returns {Object} | ||
*/ | ||
getLastToken: function() { | ||
return this._tokens[this._tokens.length - 1]; | ||
}, | ||
/** | ||
* Returns the first token before the given. | ||
* | ||
* @param {Object} token | ||
* @param {Object} [options] | ||
* @param {Boolean} [options.includeComments=false] | ||
* @returns {Object|undefined} | ||
*/ | ||
getPrevToken: function(token) { | ||
getPrevToken: function(token, options) { | ||
var index = token._tokenIndex - 1; | ||
return index >= 0 ? this._tree.tokens[index] : undefined; | ||
if (index < 0) { | ||
return undefined; | ||
} | ||
if (options && options.includeComments) { | ||
return this._tokens[index]; | ||
} | ||
do { | ||
if (!this._tokens[index].isComment) { | ||
return this._tokens[index]; | ||
} | ||
} while (--index >= 0); | ||
return undefined; | ||
}, | ||
/** | ||
@@ -271,8 +276,24 @@ * Returns the first token after the given. | ||
* @param {Object} token | ||
* @param {Object} [options] | ||
* @param {Boolean} [options.includeComments=false] | ||
* @returns {Object|undefined} | ||
*/ | ||
getNextToken: function(token) { | ||
getNextToken: function(token, options) { | ||
var index = token._tokenIndex + 1; | ||
return index < this._tree.tokens.length ? this._tree.tokens[index] : undefined; | ||
if (index >= this._tokens.length) { | ||
return undefined; | ||
} | ||
if (options && options.includeComments) { | ||
return this._tokens[index]; | ||
} | ||
do { | ||
if (!this._tokens[index].isComment) { | ||
return this._tokens[index]; | ||
} | ||
} while (++index < this._tokens.length); | ||
}, | ||
/** | ||
@@ -296,2 +317,3 @@ * Returns the first token before the given which matches type (and value). | ||
}, | ||
/** | ||
@@ -315,2 +337,3 @@ * Returns the first token after the given which matches type (and value). | ||
}, | ||
/** | ||
@@ -326,2 +349,3 @@ * Returns the first token before the given which matches type (and value). | ||
}, | ||
/** | ||
@@ -337,2 +361,3 @@ * Returns the first token after the given which matches type (and value). | ||
}, | ||
/** | ||
@@ -348,2 +373,3 @@ * Iterates through the token tree using tree iterator. | ||
}, | ||
/** | ||
@@ -366,2 +392,3 @@ * Returns node by its range position | ||
}, | ||
/** | ||
@@ -387,2 +414,3 @@ * Returns nodes by type(s) from earlier built index. | ||
}, | ||
/** | ||
@@ -398,2 +426,3 @@ * Iterates nodes by type(s) from earlier built index. | ||
}, | ||
/** | ||
@@ -419,2 +448,3 @@ * Iterates tokens by type(s) from the token array. | ||
}, | ||
/** | ||
@@ -440,28 +470,55 @@ * Iterates token by value from the token array. | ||
}, | ||
/** | ||
* Returns comment node directly followed by a token | ||
* Iterates tokens by type and value(s) from the token array. | ||
* Calls passed function for every matched token. | ||
* | ||
* @param {Object} token | ||
* @returns {Object|undefined} | ||
* @param {String} type | ||
* @param {String|String[]} value | ||
* @param {Function} cb | ||
*/ | ||
getCommentAfterToken: function(token) { | ||
if (!this._tokenCommentsAfterIndex) { | ||
this._buildCommentIndex(); | ||
} | ||
var comments = this._tokenCommentsAfterIndex[token._tokenIndex]; | ||
return comments ? comments[0] : undefined; | ||
iterateTokensByTypeAndValue: function(type, value, cb) { | ||
var values = (typeof value === 'string') ? [value] : value; | ||
var valueIndex = {}; | ||
values.forEach(function(type) { | ||
valueIndex[type] = true; | ||
}); | ||
this.getTokens().forEach(function(token, index, tokens) { | ||
if (token.type === type && valueIndex[token.value]) { | ||
cb(token, index, tokens); | ||
} | ||
}); | ||
}, | ||
/** | ||
* Returns comment node directly followed by a token | ||
* Returns first token for the specified line. | ||
* Line numbers start with 1. | ||
* | ||
* @param {Object} token | ||
* @param {Number} lineNumber | ||
* @param {Object} [options] | ||
* @param {Boolean} [options.includeComments] | ||
* @returns {Object|undefined} | ||
*/ | ||
getCommentBeforeToken: function(token) { | ||
if (!this._tokenCommentsBeforeIndex) { | ||
this._buildCommentIndex(); | ||
getFirstTokenOnLine: function(lineNumber, options) { | ||
var tokensByLine = this._tokensByLineIndex[lineNumber]; | ||
if (!tokensByLine) { | ||
return undefined; | ||
} | ||
var comments = this._tokenCommentsBeforeIndex[token._tokenIndex]; | ||
return comments ? comments[comments.length - 1] : undefined; | ||
if (options && options.includeComments) { | ||
return tokensByLine[0]; | ||
} | ||
for (var i = 0; i < tokensByLine.length; i++) { | ||
var token = tokensByLine[i]; | ||
if (!token.isComment) { | ||
return token; | ||
} | ||
} | ||
return undefined; | ||
}, | ||
/** | ||
@@ -483,2 +540,3 @@ * Returns which dialect of JS this file supports. | ||
}, | ||
/** | ||
@@ -492,2 +550,3 @@ * Returns string representing contents of the file. | ||
}, | ||
/** | ||
@@ -501,2 +560,3 @@ * Returns token tree, built using esprima. | ||
}, | ||
/** | ||
@@ -508,4 +568,5 @@ * Returns token list, built using esprima. | ||
getTokens: function() { | ||
return this._tree.tokens; | ||
return this._tokens; | ||
}, | ||
/** | ||
@@ -517,2 +578,3 @@ * Returns comment token list, built using esprima. | ||
}, | ||
/** | ||
@@ -526,2 +588,3 @@ * Returns source filename for this object representation. | ||
}, | ||
/** | ||
@@ -535,10 +598,9 @@ * Returns array of source lines for the file. | ||
}, | ||
/** | ||
* Returns array of source lines for the file with comments removed. | ||
* Will report erroneous trailing tokens in multiline comments if an error reporter is provided. | ||
* | ||
* @param {Errors} [errors=null] errors | ||
* @returns {Array} | ||
*/ | ||
getLinesWithCommentsRemoved: function(errors) { | ||
getLinesWithCommentsRemoved: function() { | ||
var lines = this.getLines().concat(); | ||
@@ -561,17 +623,240 @@ | ||
lines[x] = lines[x].substring(endCol); | ||
} | ||
}); | ||
if (errors && lines[x] !== '') { | ||
errors.add( | ||
'Multiline comments should not have tokens on its ending line', | ||
x + 1, | ||
endCol | ||
); | ||
} | ||
return lines; | ||
}, | ||
/** | ||
* Renders JS-file sources using token list. | ||
* | ||
* @returns {String} | ||
*/ | ||
render: function() { | ||
var result = ''; | ||
// For-loop for maximal speed. | ||
for (var i = 0; i < this._tokens.length; i++) { | ||
var token = this._tokens[i]; | ||
result += token.whitespaceBefore; | ||
switch (token.type) { | ||
// Line-comment: // ... | ||
case 'Line': | ||
result += '//' + token.value; | ||
break; | ||
// Block-comment: /* ... */ | ||
case 'Block': | ||
result += '/*' + token.value + '*/'; | ||
break; | ||
default: | ||
result += token.value; | ||
} | ||
} | ||
return result; | ||
}, | ||
/** | ||
* Builds token list using both code tokens and comment-tokens. | ||
* | ||
* @returns {Object[]} | ||
* @private | ||
*/ | ||
_buildTokenList: function(codeTokens, commentTokens) { | ||
var result = []; | ||
var codeQueue = codeTokens.concat(); | ||
var commentQueue = commentTokens.concat(); | ||
while (codeQueue.length > 0 || commentQueue.length > 0) { | ||
if (codeQueue.length > 0 && (!commentQueue.length || commentQueue[0].range[0] > codeQueue[0].range[0])) { | ||
result.push(codeQueue.shift()); | ||
} else { | ||
var commentToken = commentQueue.shift(); | ||
commentToken.isComment = true; | ||
result.push(commentToken); | ||
} | ||
} | ||
return result; | ||
}, | ||
/** | ||
* Adds JSCS-specific EOF (end of file) token. | ||
* | ||
* @private | ||
*/ | ||
_addEOFToken: function() { | ||
var loc = { | ||
line: this._lines.length, | ||
column: this._lines[this._lines.length - 1].length | ||
}; | ||
this._tokens.push({ | ||
type: 'EOF', | ||
value: '', | ||
range: [this._source.length, this._source.length + 1], | ||
loc: {start: loc, end: loc} | ||
}); | ||
}, | ||
return lines; | ||
/** | ||
* Applies whitespace information to the token list. | ||
* | ||
* @param {Object[]} tokens | ||
* @param {String} source | ||
* @private | ||
*/ | ||
_applyWhitespaceData: function(tokens, source) { | ||
var prevPos = 0; | ||
// For-loop for maximal speed. | ||
for (var i = 0; i < tokens.length; i++) { | ||
var token = tokens[i]; | ||
var rangeStart = token.range[0]; | ||
var whitespace; | ||
if (rangeStart === prevPos) { | ||
whitespace = ''; | ||
} else { | ||
whitespace = source.substring(prevPos, rangeStart); | ||
} | ||
token.whitespaceBefore = whitespace; | ||
prevPos = token.range[1]; | ||
} | ||
}, | ||
/** | ||
* Temporary fix (I hope) for esprima tokenizer, which results | ||
* in duplicate tokens on `export default function() {}` | ||
* (https://code.google.com/p/esprima/issues/detail?id=631) | ||
* | ||
* @param {Object[]} tokens | ||
* @returns {Object[]} | ||
* @private | ||
*/ | ||
_fixEs6Tokens: function(tokens) { | ||
if (this.getDialect() !== 'es6') { | ||
return tokens; | ||
} | ||
var tokenHash = {}; | ||
return tokens.filter(function(token) { | ||
var hashKey = token.range[0] + '_' + token.range[1]; | ||
var isDuplicate = tokenHash[hashKey]; | ||
tokenHash[hashKey] = true; | ||
return !isDuplicate; | ||
}); | ||
}, | ||
/** | ||
* Builds node index using node type as the key. | ||
* | ||
* @returns {Object} | ||
* @private | ||
*/ | ||
_buildNodeIndex: function() { | ||
var index = {}; | ||
this.iterate(function(node, parentNode, parentCollection) { | ||
var type = node.type; | ||
node.parentNode = parentNode; | ||
node.parentCollection = parentCollection; | ||
(index[type] || (index[type] = [])).push(node); | ||
}); | ||
return index; | ||
}, | ||
/** | ||
* Temporary fix (I hope) for esprima tokenizer | ||
* (https://code.google.com/p/esprima/issues/detail?id=481) | ||
* Fixes #83, #180 | ||
* @private | ||
*/ | ||
_fixEsprimaIdentifiers: function() { | ||
var _this = this; | ||
this.iterateNodesByType(['Property', 'MemberExpression'], function(node) { | ||
switch (node.type) { | ||
case 'Property': | ||
convertKeywordToIdentifierIfRequired(node.key); | ||
break; | ||
case 'MemberExpression': | ||
convertKeywordToIdentifierIfRequired(node.property); | ||
break; | ||
} | ||
}); | ||
function convertKeywordToIdentifierIfRequired(node) { | ||
var token = _this.getTokenByRangeStart(node.range[0]); | ||
if (token.type === 'Keyword') { | ||
token.type = 'Identifier'; | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* Parses a JS-file. | ||
* | ||
* @param {String} source | ||
* @param {Object} esprima | ||
* @param {Object} [esprimaOptions] | ||
* @returns {Object} | ||
*/ | ||
JsFile.parse = function(source, esprima, esprimaOptions) { | ||
var finalEsprimaOptions = { | ||
tolerant: true | ||
}; | ||
if (esprimaOptions) { | ||
for (var key in esprimaOptions) { | ||
finalEsprimaOptions[key] = esprimaOptions[key]; | ||
} | ||
} | ||
// Set required options | ||
finalEsprimaOptions.loc = true; | ||
finalEsprimaOptions.range = true; | ||
finalEsprimaOptions.comment = true; | ||
finalEsprimaOptions.tokens = true; | ||
finalEsprimaOptions.sourceType = 'module'; | ||
var hashbang = source.indexOf('#!') === 0; | ||
var tree; | ||
// Convert bin annotation to a comment | ||
if (hashbang) { | ||
source = '//' + source.substr(2); | ||
} | ||
var instrumentationData = {}; | ||
var hasInstrumentationData = false; | ||
// Process special case code like iOS instrumentation imports: `#import 'abc.js';` | ||
source = source.replace(/^#!?[^\n]+\n/gm, function(str, pos) { | ||
hasInstrumentationData = true; | ||
instrumentationData[pos] = str.substring(0, str.length - 1); | ||
return '//' + str.slice(2); | ||
}); | ||
tree = esprima.parse(source, finalEsprimaOptions); | ||
// Change the bin annotation comment | ||
if (hashbang) { | ||
tree.comments[0].type = 'Hashbang'; | ||
tree.comments[0].value = '#!' + tree.comments[0].value; | ||
} | ||
if (hasInstrumentationData) { | ||
tree.comments.forEach(function(token) { | ||
var rangeStart = token.range[0]; | ||
if (instrumentationData.hasOwnProperty(rangeStart)) { | ||
token.type = 'InstrumentationDirective'; | ||
token.value = instrumentationData[rangeStart]; | ||
} | ||
}); | ||
} | ||
return tree; | ||
}; | ||
module.exports = JsFile; |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -45,6 +45,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(disallowAnonymousFunctions) { | ||
configure: function(options) { | ||
assert( | ||
disallowAnonymousFunctions === true, | ||
'disallowAnonymousFunctions option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -60,3 +60,3 @@ }, | ||
if (node.id === null) { | ||
errors.add('Anonymous functions needs to be named', node.loc.start); | ||
errors.add('Anonymous functions need to be named', node.loc.start); | ||
} | ||
@@ -63,0 +63,0 @@ }); |
@@ -45,6 +45,6 @@ /** | ||
module.exports.prototype = { | ||
configure: function(disallowCapitalizedComments) { | ||
configure: function(options) { | ||
assert( | ||
disallowCapitalizedComments === true, | ||
'disallowCapitalizedComments option requires a value of true or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -61,3 +61,3 @@ }, | ||
file.getComments().forEach(function(comment) { | ||
file.iterateTokensByType(['Line', 'Block'], function(comment) { | ||
var stripped = comment.value.replace(/[\n\s\*]/g, ''); | ||
@@ -64,0 +64,0 @@ var firstChar = stripped[0]; |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -43,11 +43,7 @@ * JSHint: [`laxcomma`](http://www.jshint.com/docs/options/#laxcomma) | ||
configure: function(disallowCommaBeforeLineBreak) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowCommaBeforeLineBreak === 'boolean', | ||
'disallowCommaBeforeLineBreak option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowCommaBeforeLineBreak === true, | ||
'disallowCommaBeforeLineBreak option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -60,10 +56,8 @@ | ||
check: function(file, errors) { | ||
file.iterateTokensByType('Punctuator', function(token) { | ||
if (token.value === ',') { | ||
errors.assert.sameLine({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Commas should be placed on new line' | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Punctuator', ',', function(token) { | ||
errors.assert.sameLine({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Commas should be placed on new line' | ||
}); | ||
}); | ||
@@ -70,0 +64,0 @@ } |
/** | ||
* Disallows curly braces after statements. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -6,0 +6,0 @@ * Values: Array of quoted keywords or `true` to disallow curly braces after the following keywords: |
@@ -11,3 +11,3 @@ /** | ||
* | ||
* Type: `Boolean` or `Object` | ||
* Types: `Boolean` or `Object` | ||
* | ||
@@ -24,3 +24,3 @@ * Values: | ||
* ```js | ||
* "disallowDanglingUnderscores": { allExcept: ["_exception"] } | ||
* "disallowDanglingUnderscores": { "allExcept": ["_exception"] } | ||
* ``` | ||
@@ -63,3 +63,3 @@ * | ||
// verify first item in `allExcept` property in object (if it's an object) | ||
// verify first item in `allExcept` property in object (if it's an object) | ||
assert( | ||
@@ -69,3 +69,3 @@ typeof identifiers !== 'object' || | ||
typeof identifiers.allExcept[0] === 'string', | ||
'Property `allExcept` in requireSpaceAfterLineComment should be an array of strings' | ||
'Property `allExcept` in ' + this.getOptionName() + ' should be an array of strings' | ||
); | ||
@@ -72,0 +72,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -37,11 +37,7 @@ * JSHint: [`noempty`](http://jshint.com/docs/options/#noempty) | ||
configure: function(disallowEmptyBlocks) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowEmptyBlocks === 'boolean', | ||
'disallowEmptyBlocks option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowEmptyBlocks === true, | ||
'disallowEmptyBlocks option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -48,0 +44,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -45,6 +45,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(disallowFunctionDeclarations) { | ||
configure: function(options) { | ||
assert( | ||
disallowFunctionDeclarations === true, | ||
'disallowFunctionDeclarations option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -51,0 +51,0 @@ }, |
@@ -40,3 +40,3 @@ /** | ||
configure: function(types) { | ||
assert(Array.isArray(types), 'disallowImplicitTypeConversion option requires array value'); | ||
assert(Array.isArray(types), this.getOptionName() + ' option requires array value'); | ||
this._typeIndex = {}; | ||
@@ -43,0 +43,0 @@ for (var i = 0, l = types.length; i < l; i++) { |
/** | ||
* Disallows keywords in your comments, such as TODO or FIXME | ||
* | ||
* Type: `Boolean` or `String` or `Array` | ||
* Types: `Boolean`, `String` or `Array` | ||
* | ||
@@ -64,3 +64,3 @@ * Values: | ||
module.exports.prototype = { | ||
configure: function(disallowKeywordsInComments) { | ||
configure: function(keywords) { | ||
this._message = 'Comments cannot contain the following keywords: '; | ||
@@ -70,7 +70,7 @@ this._keywords = ['todo', 'fixme']; | ||
switch (true) { | ||
case Array.isArray(disallowKeywordsInComments): | ||
case Array.isArray(keywords): | ||
// use the array of strings provided to build RegExp pattern | ||
this._keywords = disallowKeywordsInComments; | ||
this._keywords = keywords; | ||
/* falls through */ | ||
case disallowKeywordsInComments: | ||
case keywords: | ||
// use default keywords | ||
@@ -80,9 +80,9 @@ this._message += this._keywords.join(', '); | ||
break; | ||
case typeof disallowKeywordsInComments === 'string': | ||
case typeof keywords === 'string': | ||
// use string passed in as the RegExp pattern | ||
this._message = 'Comments cannot contain keywords based on the expression you provided'; | ||
this._keywordRegEx = new RegExp(disallowKeywordsInComments, 'gi'); | ||
this._keywordRegEx = new RegExp(keywords, 'gi'); | ||
break; | ||
default: | ||
assert(false, 'disallowKeywordsInComments option requires a value of true, a string or an array'); | ||
assert(false, this.getOptionName() + ' option requires a true value, a string or an array'); | ||
} | ||
@@ -96,3 +96,3 @@ }, | ||
check: function(file, errors) { | ||
file.getComments().forEach(function(comment) { | ||
file.iterateTokensByType(['Line', 'Block'], function(comment) { | ||
getCommentErrors(comment, this._keywordRegEx).forEach(function(errorObj) { | ||
@@ -103,2 +103,2 @@ errors.add(this._message, errorObj); | ||
} | ||
}; | ||
}; |
@@ -43,7 +43,4 @@ /** | ||
configure: function(keywords) { | ||
assert(Array.isArray(keywords), 'disallowKeywordsOnNewLine option requires array value'); | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
assert(Array.isArray(keywords), this.getOptionName() + ' option requires array value'); | ||
this._keywords = keywords; | ||
}, | ||
@@ -56,19 +53,15 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
var prevToken = file.getPrevToken(token); | ||
file.iterateTokensByType('Keyword', function(token) { | ||
if (keywordIndex[token.value]) { | ||
var prevToken = file.getPrevToken(token); | ||
// Special case for #905, even though it contradicts rule meaning, | ||
// it makes more sense that way. | ||
if (token.value === 'else' && prevToken.value !== '}') { | ||
return; | ||
} | ||
// Special case for #905, even though it contradicts rule meaning, | ||
// it makes more sense that way. | ||
if (token.value === 'else' && prevToken.value !== '}') { | ||
return; | ||
} | ||
errors.assert.sameLine({ | ||
token: file.getPrevToken(token), | ||
nextToken: token | ||
}); | ||
} | ||
errors.assert.sameLine({ | ||
token: prevToken, | ||
nextToken: token | ||
}); | ||
}); | ||
@@ -75,0 +68,0 @@ } |
@@ -30,7 +30,4 @@ /** | ||
configure: function(keywords) { | ||
assert(Array.isArray(keywords), 'disallowKeywords option requires array value'); | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
assert(Array.isArray(keywords), this.getOptionName() + ' option requires array value'); | ||
this._keywords = keywords; | ||
}, | ||
@@ -43,11 +40,7 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByType('Keyword', function(token) { | ||
if (keywordIndex[token.value]) { | ||
errors.add( | ||
'Illegal keyword: ' + token.value, | ||
token.loc.start | ||
); | ||
} | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
errors.add( | ||
'Illegal keyword: ' + token.value, | ||
token.loc.start | ||
); | ||
}); | ||
@@ -54,0 +47,0 @@ } |
@@ -5,3 +5,3 @@ /** | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -59,9 +59,9 @@ * Values: `true` or `"smart"` | ||
configure: function(disallowMixedSpacesAndTabs) { | ||
configure: function(options) { | ||
assert( | ||
disallowMixedSpacesAndTabs === true || disallowMixedSpacesAndTabs === 'smart', | ||
'disallowMixedSpacesAndTabs option requires true or "smart" value' | ||
options === true || options === 'smart', | ||
this.getOptionName() + ' option requires a true value or "smart"' | ||
); | ||
this._disallowMixedSpacesAndTabs = disallowMixedSpacesAndTabs; | ||
this._options = options; | ||
}, | ||
@@ -74,3 +74,3 @@ | ||
check: function(file, errors) { | ||
var test = this._disallowMixedSpacesAndTabs === true ? | ||
var test = this._options === true ? | ||
(/ \t|\t [^\*]|\t $/) : | ||
@@ -77,0 +77,0 @@ (/ \t/); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -37,11 +37,7 @@ * #### Example | ||
configure: function(disallowMultipleLineBreaks) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowMultipleLineBreaks === 'boolean', | ||
'disallowMultipleLineBreaks option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowMultipleLineBreaks === true, | ||
'disallowMultipleLineBreaks option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -54,12 +50,18 @@ | ||
check: function(file, errors) { | ||
var lines = file.getLines(); | ||
for (var i = 1, l = lines.length; i < l; i++) { | ||
var line = lines[i]; | ||
if (line === '' && lines[i - 1] === '') { | ||
while (++i < l && lines[i] === '') {} | ||
errors.add('Multiple line break', i - 1, 0); | ||
// Iterate over all tokens (including comments) | ||
file.getTokens().forEach(function(token, index, tokens) { | ||
// If there are no trailing tokens, exit early | ||
var nextToken = tokens[index + 1]; | ||
if (!nextToken) { | ||
return; | ||
} | ||
} | ||
errors.assert.linesBetween({ | ||
token: token, | ||
nextToken: nextToken, | ||
atMost: 2, | ||
}); | ||
}); | ||
} | ||
}; |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -37,11 +37,7 @@ * JSHint: [`multistr`](http://www.jshint.com/docs/options/#multistr) | ||
configure: function(disallowMultipleLineStrings) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowMultipleLineStrings === 'boolean', | ||
'disallowMultipleLineStrings option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowMultipleLineStrings === true, | ||
'disallowMultipleLineStrings option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -48,0 +44,0 @@ |
/** | ||
* Disallows multiple `var` declaration (except for-loop). | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -60,12 +60,12 @@ * Values: | ||
configure: function(disallowMultipleVarDecl) { | ||
configure: function(options) { | ||
assert( | ||
disallowMultipleVarDecl === true || | ||
disallowMultipleVarDecl === 'strict' || | ||
disallowMultipleVarDecl === 'exceptUndefined', | ||
'disallowMultipleVarDecl option requires true, "strict", or "exceptUndefined" value' | ||
options === true || | ||
options === 'strict' || | ||
options === 'exceptUndefined', | ||
this.getOptionName() + ' option requires a true value, "strict", or "exceptUndefined"' | ||
); | ||
this.strictMode = disallowMultipleVarDecl === 'strict'; | ||
this.exceptUndefined = disallowMultipleVarDecl === 'exceptUndefined'; | ||
this.strictMode = options === 'strict'; | ||
this.exceptUndefined = options === 'exceptUndefined'; | ||
}, | ||
@@ -72,0 +72,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -78,11 +78,7 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(disallowNewlineBeforeBlockStatements) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowNewlineBeforeBlockStatements === 'boolean', | ||
'disallowNewlineBeforeBlockStatements option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowNewlineBeforeBlockStatements === true, | ||
'disallowNewlineBeforeBlockStatements option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -89,0 +85,0 @@ |
/** | ||
* Requires putting certain operators on the next line rather than on the current line before a line break. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -43,3 +43,3 @@ * Values: Array of operators to apply to or `true` | ||
assert(Array.isArray(operators) || operators === true, | ||
'disallowOperatorBeforeLineBreak option requires array or true value'); | ||
this.getOptionName() + ' option requires array or true value'); | ||
@@ -57,20 +57,10 @@ if (operators === true) { | ||
check: function(file, errors) { | ||
var tokens = file.getTokens(); | ||
var lastToken; | ||
var operators = this._operators; | ||
tokens.forEach(function(token) { | ||
if (lastToken) { | ||
if (lastToken.type === 'Punctuator' && operators.indexOf(lastToken.value) > -1) { | ||
if (lastToken.loc.end.line < token.loc.end.line) { | ||
errors.add( | ||
'Operator needs to either be on the same line or after a line break.', | ||
token.loc.start | ||
); | ||
} | ||
} | ||
} | ||
lastToken = token; | ||
file.iterateTokensByTypeAndValue('Punctuator', this._operators, function(token) { | ||
errors.assert.sameLine({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Operator needs to either be on the same line or after a line break.' | ||
}); | ||
}); | ||
} | ||
}; |
/** | ||
* Disallow an empty line above the specified keywords. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -75,3 +75,3 @@ * Values: Array of quoted types or `true` to disallow padding new lines after all of the keywords below. | ||
assert(Array.isArray(keywords) || keywords === true, | ||
'disallowPaddingNewlinesBeforeKeywords option requires array or true value'); | ||
this.getOptionName() + ' option requires array or true value'); | ||
@@ -82,6 +82,3 @@ if (keywords === true) { | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
this._keywords = keywords; | ||
}, | ||
@@ -94,18 +91,11 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByType('Keyword', function(token) { | ||
if (keywordIndex[token.value]) { | ||
var prevToken = file.getPrevToken(token); | ||
if (prevToken && token.loc.start.line - prevToken.loc.end.line > 1) { | ||
errors.add( | ||
'Keyword `' + token.value + '` should not have an empty line above it', | ||
token.loc.start.line, | ||
token.loc.start.column | ||
); | ||
} | ||
} | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
errors.assert.linesBetween({ | ||
token: file.getPrevToken(token), | ||
nextToken: token, | ||
atMost: 1, | ||
message: 'Keyword `' + token.value + '` should not have an empty line above it' | ||
}); | ||
}); | ||
} | ||
}; |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` validates all non-empty blocks. | ||
* Value: `true` validates all non-empty blocks. | ||
* | ||
@@ -42,6 +42,6 @@ * #### Example | ||
configure: function(disallowPaddingNewlinesInBlocks) { | ||
configure: function(options) { | ||
assert( | ||
disallowPaddingNewlinesInBlocks === true, | ||
'disallowPaddingNewlinesInBlocks option requires the value true or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -55,22 +55,20 @@ }, | ||
check: function(file, errors) { | ||
var lines = file.getLines(); | ||
file.iterateNodesByType('BlockStatement', function(node) { | ||
var openingBracket = file.getFirstNodeToken(node); | ||
var startLine = openingBracket.loc.start.line; | ||
errors.assert.linesBetween({ | ||
token: openingBracket, | ||
nextToken: file.getNextToken(openingBracket, {includeComments: true}), | ||
atMost: 1, | ||
message: 'Expected no padding newline after opening curly brace' | ||
}); | ||
var closingBracket = file.getLastNodeToken(node); | ||
var closingLine = closingBracket.loc.start.line; | ||
if (startLine === closingLine) { | ||
return; | ||
} | ||
if (lines[startLine] === '') { | ||
errors.add('Expected no padding newline after opening curly brace', openingBracket.loc.end); | ||
} | ||
if (lines[closingLine - 2] === '') { | ||
errors.add('Expected no padding newline before closing curly brace', closingBracket.loc.start); | ||
} | ||
errors.assert.linesBetween({ | ||
token: file.getPrevToken(closingBracket, {includeComments: true}), | ||
nextToken: closingBracket, | ||
atMost: 1, | ||
message: 'Expected no padding newline before closing curly brace' | ||
}); | ||
}); | ||
@@ -77,0 +75,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -44,8 +44,4 @@ * #### Example | ||
assert( | ||
typeof value === 'boolean', | ||
'disallowPaddingNewLinesInObjects option requires boolean value' | ||
); | ||
assert( | ||
value === true, | ||
'disallowPaddingNewLinesInObjects option requires true value or should be removed' | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -67,12 +63,15 @@ }, | ||
if (openingBracket.loc.end.line !== nextToken.loc.start.line) { | ||
errors.add('Illegal newline after opening curly brace', nextToken.loc.start); | ||
} | ||
errors.assert.sameLine({ | ||
token: openingBracket, | ||
nextToken: nextToken, | ||
message: 'Illegal newline after opening curly brace' | ||
}); | ||
var closingBracket = file.getLastNodeToken(node); | ||
var prevToken = file.getPrevToken(closingBracket); | ||
if (closingBracket.loc.start.line !== prevToken.loc.end.line) { | ||
errors.add('Illegal newline before closing curly brace', closingBracket.loc.start); | ||
} | ||
errors.assert.sameLine({ | ||
token: file.getPrevToken(closingBracket), | ||
nextToken: closingBracket, | ||
message: 'Illegal newline before closing curly brace' | ||
}); | ||
}); | ||
@@ -79,0 +78,0 @@ } |
/** | ||
* Disallows quoted keys in object if possible. | ||
* | ||
* Type: `String` or `Boolean` | ||
* Types: `String` or `Boolean` | ||
* | ||
@@ -44,9 +44,9 @@ * Values: | ||
configure: function(disallowQuotedKeysInObjects) { | ||
configure: function(options) { | ||
assert( | ||
disallowQuotedKeysInObjects === true || disallowQuotedKeysInObjects === 'allButReserved', | ||
this.getOptionName() + ' options should be true or "allButReserved" value' | ||
options === true || options === 'allButReserved', | ||
this.getOptionName() + ' option requires a true value or "allButReserved"' | ||
); | ||
this._mode = disallowQuotedKeysInObjects; | ||
this._mode = options; | ||
}, | ||
@@ -53,0 +53,0 @@ |
@@ -34,6 +34,6 @@ /** | ||
module.exports.prototype = { | ||
configure: function(disallowSemicolons) { | ||
configure: function(options) { | ||
assert( | ||
disallowSemicolons === true, | ||
'disallowSemicolons option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -47,13 +47,10 @@ }, | ||
check: function(file, errors) { | ||
file.getTokens() | ||
.filter(function(token) { | ||
return token.type === 'Punctuator' && token.value === ';'; | ||
}) | ||
.forEach(function(token) { | ||
var nextToken = file.getNextToken(token); | ||
if (!nextToken || nextToken.loc.end.line > token.loc.end.line) { | ||
errors.add('semicolons are disallowed at the end of a line.', token.loc.end); | ||
} | ||
}); | ||
file.iterateTokensByTypeAndValue('Punctuator', ';', function(token) { | ||
var nextToken = file.getNextToken(token); | ||
// do not use assertions here as this is not yet autofixable | ||
if (nextToken.type === 'EOF' || nextToken.loc.end.line > token.loc.end.line) { | ||
errors.add('semicolons are disallowed at the end of a line.', token.loc.end); | ||
} | ||
}); | ||
} | ||
}; |
/** | ||
* Requires sticking binary operators to the right. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -50,3 +50,3 @@ * Values: Array of quoted operators or `true` to disallow space after all possible binary operators | ||
Array.isArray(operators) || isTrue, | ||
'disallowSpaceAfterBinaryOperators option requires array or true value' | ||
this.getOptionName() + ' option requires array or true value' | ||
); | ||
@@ -73,10 +73,8 @@ | ||
if (operators[',']) { | ||
file.iterateTokensByType('Punctuator', function(token) { | ||
if (token.value === ',') { | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Operator , should stick to following expression' | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Punctuator', ',', function(token) { | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Operator , should stick to following expression' | ||
}); | ||
}); | ||
@@ -83,0 +81,0 @@ } |
/** | ||
* Disallows space after keyword. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -50,3 +50,3 @@ * Values: Array of quoted keywords or `true` to disallow spaces after all possible keywords. | ||
Array.isArray(keywords) || keywords === true, | ||
'disallowSpaceAfterKeywords option requires array or true value' | ||
this.getOptionName() + ' option requires array or true value' | ||
); | ||
@@ -58,6 +58,3 @@ | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
this._keywords = keywords; | ||
}, | ||
@@ -70,11 +67,7 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByType('Keyword', function(token) { | ||
if (keywordIndex[token.value]) { | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: file.getNextToken(token) | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: file.getNextToken(token) | ||
}); | ||
}); | ||
@@ -81,0 +74,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -35,6 +35,6 @@ * #### Example | ||
configure: function(disallowSpaceAfterLineComment) { | ||
configure: function(options) { | ||
assert( | ||
disallowSpaceAfterLineComment === true, | ||
'disallowSpaceAfterLineComment option requires the value true' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -48,10 +48,6 @@ }, | ||
check: function(file, errors) { | ||
var comments = file.getComments(); | ||
comments.forEach(function(comment) { | ||
if (comment.type === 'Line') { | ||
var value = comment.value; | ||
if (value.length > 0 && value[0] === ' ') { | ||
errors.add('Illegal space after line comment', comment.loc.start); | ||
} | ||
file.iterateTokensByType('Line', function(comment) { | ||
var value = comment.value; | ||
if (value.length > 0 && value[0] === ' ') { | ||
errors.add('Illegal space after line comment', comment.loc.start); | ||
} | ||
@@ -58,0 +54,0 @@ }); |
/** | ||
* Disallows space after object keys. | ||
* | ||
* Type: `Boolean` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -6,0 +6,0 @@ * Values: |
/** | ||
* Requires sticking unary operators to the right. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -6,0 +6,0 @@ * Values: Array of quoted operators or `true` to disallow space after prefix for all unary operators |
/** | ||
* Requires sticking binary operators to the left. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -51,3 +51,3 @@ * Values: Array of quoted operators or `true` to disallow space before all possible binary operators | ||
Array.isArray(operators) || isTrue, | ||
'disallowSpaceBeforeBinaryOperators option requires array or true value' | ||
this.getOptionName() + ' option requires array or true value' | ||
); | ||
@@ -74,10 +74,8 @@ | ||
if (operators[',']) { | ||
file.iterateTokensByType('Punctuator', function(token) { | ||
if (token.value === ',') { | ||
errors.assert.noWhitespaceBetween({ | ||
token: file.getPrevToken(token), | ||
nextToken: token, | ||
message: 'Operator , should stick to previous expression' | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Punctuator', ',', function(token) { | ||
errors.assert.noWhitespaceBetween({ | ||
token: file.getPrevToken(token, {includeComments: true}), | ||
nextToken: token, | ||
message: 'Operator , should stick to previous expression' | ||
}); | ||
}); | ||
@@ -110,3 +108,3 @@ } | ||
var prevToken = file.getPrevToken(operatorToken); | ||
var prevToken = file.getPrevToken(operatorToken, {includeComments: true}); | ||
@@ -113,0 +111,0 @@ if (operators[operator]) { |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -57,11 +57,7 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(disallowSpaceBeforeBlockStatements) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowSpaceBeforeBlockStatements === 'boolean', | ||
'disallowSpaceBeforeBlockStatements option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowSpaceBeforeBlockStatements === true, | ||
'disallowSpaceBeforeBlockStatements option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -68,0 +64,0 @@ |
/** | ||
* Disallows space before keyword. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -45,3 +45,3 @@ * Values: Array of quoted keywords or `true` to disallow spaces before all possible keywords. | ||
Array.isArray(keywords) || keywords === true, | ||
'disallowSpaceBeforeKeywords option requires array or true value'); | ||
this.getOptionName() + ' option requires array or true value'); | ||
@@ -52,6 +52,3 @@ if (keywords === true) { | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
this._keywords = keywords; | ||
}, | ||
@@ -64,20 +61,14 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
var prevToken = file.getPrevToken(token, {includeComments: true}); | ||
if (!prevToken || prevToken.isComment) { | ||
return; | ||
} | ||
file.iterateTokensByType(['Keyword'], function(token) { | ||
if (keywordIndex[token.value]) { | ||
var prevToken = file.getPrevToken(token); | ||
if (!prevToken) { | ||
return; | ||
} | ||
prevToken = file.getCommentBeforeToken(token) || prevToken; | ||
if (prevToken.type !== 'Keyword' && prevToken.value !== ';') { | ||
errors.assert.noWhitespaceBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
message: 'Illegal space before "' + token.value + '" keyword' | ||
}); | ||
} | ||
if (prevToken.type !== 'Keyword' && prevToken.value !== ';') { | ||
errors.assert.noWhitespaceBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
message: 'Illegal space before "' + token.value + '" keyword' | ||
}); | ||
} | ||
@@ -84,0 +75,0 @@ }); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -34,3 +34,3 @@ * #### Example | ||
disallow === true, | ||
this.getOptionName() + ' option requires true value or should be removed' | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -37,0 +37,0 @@ }, |
/** | ||
* Requires sticking unary operators to the left. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -6,0 +6,0 @@ * Values: Array of quoted operators or `true` to disallow space before postfix for all unary operators |
@@ -33,11 +33,7 @@ /** | ||
configure: function(disallowSpaceBetweenArguments) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowSpaceBetweenArguments === 'boolean', | ||
this.getOptionName() + ' option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowSpaceBetweenArguments === true, | ||
this.getOptionName() + ' option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -44,0 +40,0 @@ |
@@ -47,3 +47,3 @@ /** | ||
typeof options === 'object', | ||
'disallowSpacesInAnonymousFunctionExpression option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -54,3 +54,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'disallowSpacesInAnonymousFunctionExpression.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -63,3 +63,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'disallowSpacesInAnonymousFunctionExpression.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -71,3 +71,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'disallowSpacesInAnonymousFunctionExpression must have beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace ' + | ||
' or beforeOpeningRoundBrace property' | ||
@@ -97,21 +97,22 @@ ); | ||
// anonymous function expressions only | ||
if (!node.id) { | ||
if (node.id) { | ||
return; | ||
} | ||
if (beforeOpeningRoundBrace) { | ||
var functionToken = file.getFirstNodeToken(node); | ||
errors.assert.noWhitespaceBetween({ | ||
token: functionToken, | ||
nextToken: file.getNextToken(functionToken), | ||
message: 'Illegal space before opening round brace', | ||
}); | ||
} | ||
if (beforeOpeningRoundBrace) { | ||
var functionToken = file.getFirstNodeToken(node); | ||
errors.assert.noWhitespaceBetween({ | ||
token: functionToken, | ||
nextToken: file.getNextToken(functionToken), | ||
message: 'Illegal space before opening round brace', | ||
}); | ||
} | ||
if (beforeOpeningCurlyBrace) { | ||
var bodyToken = file.getFirstNodeToken(node.body); | ||
errors.assert.noWhitespaceBetween({ | ||
token: file.getPrevToken(bodyToken), | ||
nextToken: bodyToken, | ||
message: 'Illegal space before opening curly brace', | ||
}); | ||
} | ||
if (beforeOpeningCurlyBrace) { | ||
var bodyToken = file.getFirstNodeToken(node.body); | ||
errors.assert.noWhitespaceBetween({ | ||
token: file.getPrevToken(bodyToken), | ||
nextToken: bodyToken, | ||
message: 'Illegal space before opening curly brace', | ||
}); | ||
} | ||
@@ -118,0 +119,0 @@ }); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -33,6 +33,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireSpacesInCallExpression) { | ||
configure: function(options) { | ||
assert( | ||
requireSpacesInCallExpression === true, | ||
'disallowSpacesInCallExpression option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -39,0 +39,0 @@ }, |
/** | ||
* Disallows space before and/or after `?` or `:` in conditional expressions. | ||
* | ||
* Type: `Object` or `Boolean` | ||
* Types: `Object` or `Boolean` | ||
* | ||
@@ -72,3 +72,3 @@ * Values: `"afterTest"`, `"beforeConsequent"`, `"afterConsequent"`, `"beforeAlternate"` as child properties, | ||
typeof options === 'object', | ||
optionName + ' option must be an object or boolean true' | ||
optionName + ' option requires a true value or an object' | ||
); | ||
@@ -117,8 +117,8 @@ | ||
token = file.getPrevToken(questionMarkToken); | ||
if (token.loc.end.column !== questionMarkToken.loc.start.column) { | ||
errors.add( | ||
'Illegal space after test', | ||
test.loc.end | ||
); | ||
} | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: questionMarkToken, | ||
message: 'Illegal space after test' | ||
}); | ||
} | ||
@@ -128,8 +128,8 @@ | ||
token = file.getNextToken(questionMarkToken); | ||
if (token.loc.start.column !== questionMarkToken.loc.end.column) { | ||
errors.add( | ||
'Illegal space before consequent', | ||
consequent.loc.start | ||
); | ||
} | ||
errors.assert.noWhitespaceBetween({ | ||
token: questionMarkToken, | ||
nextToken: token, | ||
message: 'Illegal space before consequent' | ||
}); | ||
} | ||
@@ -139,8 +139,8 @@ | ||
token = file.getPrevToken(colonToken); | ||
if (token.loc.end.column !== colonToken.loc.start.column) { | ||
errors.add( | ||
'Illegal space after consequent', | ||
consequent.loc.end | ||
); | ||
} | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: colonToken, | ||
message: 'Illegal space after consequent' | ||
}); | ||
} | ||
@@ -150,8 +150,7 @@ | ||
token = file.getNextToken(colonToken); | ||
if (token.loc.start.column !== colonToken.loc.end.column) { | ||
errors.add( | ||
'Illegal space before alternate', | ||
alternate.loc.start | ||
); | ||
} | ||
errors.assert.noWhitespaceBetween({ | ||
token: colonToken, | ||
nextToken: token, | ||
message: 'Illegal space before alternate' | ||
}); | ||
} | ||
@@ -158,0 +157,0 @@ }.bind(this)); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` to disallow spaces in between for statement. | ||
* Value: `true` to disallow spaces in between for statement. | ||
* | ||
@@ -49,6 +49,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(disallowSpacesInForStatement) { | ||
configure: function(options) { | ||
assert( | ||
disallowSpacesInForStatement === true, | ||
'disallowSpacesInForStatement option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -55,0 +55,0 @@ }, |
@@ -27,4 +27,5 @@ /** | ||
* ```js | ||
* function a() {} | ||
* function a (){} | ||
* function a () {} | ||
* function a (){} | ||
* ``` | ||
@@ -41,3 +42,3 @@ */ | ||
typeof options === 'object', | ||
'disallowSpacesInFunctionDeclaration option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -48,3 +49,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'disallowSpacesInFunctionDeclaration.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -57,3 +58,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'disallowSpacesInFunctionDeclaration.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -65,3 +66,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'disallowSpacesInFunctionDeclaration must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
); | ||
@@ -68,0 +69,0 @@ |
@@ -29,4 +29,8 @@ /** | ||
* ```js | ||
* var x = function() {}; | ||
* var x = function (){}; | ||
* var x = function () {}; | ||
* var x = function a() {}; | ||
* var x = function a (){}; | ||
* var x = function a () {}; | ||
* ``` | ||
@@ -43,3 +47,3 @@ */ | ||
typeof options === 'object', | ||
'disallowSpacesInFunctionExpression option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -50,3 +54,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'disallowSpacesInFunctionExpression.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -59,3 +63,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'disallowSpacesInFunctionExpression.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -67,3 +71,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'disallowSpacesInFunctionExpression must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
); | ||
@@ -70,0 +74,0 @@ |
@@ -5,3 +5,3 @@ /** | ||
* Disallows space before `()` or `{}` in function expressions (both [named](#disallowspacesinnamedfunctionexpression) | ||
* and [anonymous](#disallowspacesinanonymousfunctionexpression)). | ||
* and [anonymous](#disallowspacesinanonymousfunctionexpression)) and function declarations. | ||
* | ||
@@ -16,3 +16,3 @@ * Type: `Object` | ||
* ```js | ||
* "disallowSpacesInFunctionExpression": { | ||
* "disallowSpacesInFunction": { | ||
* "beforeOpeningRoundBrace": true, | ||
@@ -28,2 +28,3 @@ * "beforeOpeningCurlyBrace": true | ||
* var x = function a(){}; | ||
* function a(){} | ||
* ``` | ||
@@ -34,4 +35,11 @@ * | ||
* ```js | ||
* var x = function() {}; | ||
* var x = function (){}; | ||
* var x = function () {}; | ||
* var x = function a() {}; | ||
* var x = function a (){}; | ||
* var x = function a () {}; | ||
* function a() {} | ||
* function a (){} | ||
* function a () {} | ||
* ``` | ||
@@ -48,3 +56,3 @@ */ | ||
typeof options === 'object', | ||
'disallowSpacesInFunction option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -55,3 +63,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'disallowSpacesInFunction.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -64,3 +72,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'disallowSpacesInFunction.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -72,3 +80,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'disallowSpacesInFunction must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
); | ||
@@ -75,0 +83,0 @@ |
@@ -27,4 +27,5 @@ /** | ||
* ```js | ||
* var x = function a() {}; | ||
* var x = function a (){}; | ||
* var x = function a () {}; | ||
* var x = function a (){}; | ||
* ``` | ||
@@ -41,3 +42,3 @@ */ | ||
typeof options === 'object', | ||
'disallowSpacesInNamedFunctionExpression option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -48,3 +49,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'disallowSpacesInNamedFunctionExpression.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -57,3 +58,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'disallowSpacesInNamedFunctionExpression.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -65,3 +66,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'disallowSpacesInNamedFunctionExpression must have beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace ' + | ||
'or beforeOpeningRoundBrace property' | ||
@@ -68,0 +69,0 @@ ); |
/** | ||
* Disallows space after opening array square bracket and before closing. | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean`, `String` or `Object` | ||
* | ||
@@ -62,3 +62,3 @@ * Values: `"all"` or `true` for strict mode, `"nested"` (*deprecated* use `"allExcept": [ "[", "]" ]`) | ||
var error = 'disallowSpacesInsideArrayBrackets rule' + | ||
var error = this.getOptionName() + ' rule' + | ||
' requires string value "all" or "nested" or object'; | ||
@@ -100,5 +100,5 @@ | ||
var openBracket = file.getFirstNodeToken(node); | ||
var afterOpen = file.getNextToken(openBracket); | ||
var afterOpen = file.getNextToken(openBracket, {includeComments: true}); | ||
var closeBracket = file.getLastNodeToken(node); | ||
var beforeClose = file.getPrevToken(closeBracket); | ||
var beforeClose = file.getPrevToken(closeBracket, {includeComments: true}); | ||
@@ -114,4 +114,3 @@ // Skip for empty array brackets | ||
nextToken: afterOpen, | ||
spaces: 1, | ||
message: 'One space required after opening bracket' | ||
message: 'Illegal space after opening bracket' | ||
}); | ||
@@ -124,4 +123,3 @@ } | ||
nextToken: closeBracket, | ||
spaces: 1, | ||
message: 'One space required before closing bracket' | ||
message: 'Illegal space before closing bracket' | ||
}); | ||
@@ -128,0 +126,0 @@ } |
/** | ||
* Disallows space after opening square bracket and before closing. | ||
* | ||
* Type: `Boolean` or `Object` | ||
* Types: `Boolean` or `Object` | ||
* | ||
@@ -50,3 +50,3 @@ * Values: `true` for strict mode, or `"allExcept": [ "[", "]" ]` | ||
var error = 'disallowSpacesInsideBrackets rule requires string value true or object'; | ||
var error = this.getOptionName() + ' rule requires string value true or object'; | ||
@@ -53,0 +53,0 @@ if (isObject) { |
/** | ||
* Disallows space after opening object curly brace and before closing. | ||
* | ||
* Type: `Object`, `Boolean` or `String` | ||
* Types: `Object`, `Boolean` or `String` | ||
* | ||
@@ -59,3 +59,3 @@ * Values: `"all"` or `true` for strict mode, `"nested"` (*deprecated* use `"allExcept": ['}']`) | ||
var error = 'disallowSpacesInsideObjectBrackets rule' + | ||
var error = this.getOptionName() + ' rule' + | ||
' requires string "all" or "nested", true value or object'; | ||
@@ -62,0 +62,0 @@ |
/** | ||
* Disallows space after opening round bracket and before closing. | ||
* | ||
* Type: `Object` or `Boolean` | ||
* Types: `Object` or `Boolean` | ||
* | ||
* Values: `true` or Object with either `"only"` with array of tokens | ||
* Values: Either `true` or Object with `"only"` property as an array of tokens | ||
* | ||
@@ -47,3 +47,3 @@ * #### Example | ||
var error = 'disallowSpacesInsideParentheses option requires' + | ||
var error = this.getOptionName() + ' option requires' + | ||
' true or object value with "only" properties '; | ||
@@ -81,10 +81,4 @@ | ||
function isCommentInRange(start, end) { | ||
return file.getComments().some(function(comment) { | ||
return start <= comment.range[0] && end >= comment.range[1]; | ||
}); | ||
} | ||
file.iterateTokenByValue('(', function(token) { | ||
var nextToken = file.getNextToken(token); | ||
var nextToken = file.getNextToken(token, {includeComments: true}); | ||
var value = nextToken.value; | ||
@@ -96,11 +90,11 @@ | ||
if (token.range[1] !== nextToken.range[0] && | ||
token.loc.end.line === nextToken.loc.start.line && | ||
!isCommentInRange(token.range[1], nextToken.range[0])) { | ||
errors.add('Illegal space after opening round bracket', token.loc.end); | ||
} | ||
errors.assert.noWhitespaceBetween({ | ||
token: token, | ||
nextToken: nextToken, | ||
message: 'Illegal space after opening round bracket' | ||
}); | ||
}); | ||
file.iterateTokenByValue(')', function(token) { | ||
var prevToken = file.getPrevToken(token); | ||
var prevToken = file.getPrevToken(token, {includeComments: true}); | ||
var value = prevToken.value; | ||
@@ -121,7 +115,7 @@ | ||
if (prevToken.range[1] !== token.range[0] && | ||
prevToken.loc.end.line === token.loc.start.line && | ||
!isCommentInRange(prevToken.range[1], token.range[0])) { | ||
errors.add('Illegal space before closing round bracket', prevToken.loc.end); | ||
} | ||
errors.assert.noWhitespaceBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
message: 'Illegal space before closing round bracket' | ||
}); | ||
}); | ||
@@ -128,0 +122,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -37,11 +37,7 @@ * JSHint: [`es3`](http://jshint.com/docs/options/#es3) | ||
module.exports.prototype = { | ||
configure: function(disallowTrailingComma) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowTrailingComma === 'boolean', | ||
'disallowTrailingComma option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowTrailingComma === true, | ||
'disallowTrailingComma option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -48,0 +44,0 @@ |
/** | ||
* Requires all lines to end on a non-whitespace character | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -53,8 +53,8 @@ * Values: | ||
configure: function(disallowTrailingWhitespace) { | ||
configure: function(options) { | ||
assert( | ||
disallowTrailingWhitespace === true || disallowTrailingWhitespace === 'ignoreEmptyLines', | ||
'disallowTrailingWhitespace option requires true value or "ignoreEmptyLines" string' | ||
options === true || options === 'ignoreEmptyLines', | ||
this.getOptionName() + ' option requires a true value or "ignoreEmptyLines"' | ||
); | ||
this._ignoreEmptyLines = disallowTrailingWhitespace === 'ignoreEmptyLines'; | ||
this._ignoreEmptyLines = options === 'ignoreEmptyLines'; | ||
}, | ||
@@ -61,0 +61,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -38,11 +38,7 @@ * #### Example | ||
configure: function(disallowYodaConditions) { | ||
configure: function(options) { | ||
assert( | ||
typeof disallowYodaConditions === 'boolean', | ||
'disallowYodaConditions option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
disallowYodaConditions === true, | ||
'disallowYodaConditions option requires true value or should be removed' | ||
); | ||
this._operatorIndex = { | ||
@@ -49,0 +45,0 @@ '==': true, |
/** | ||
* Requires all lines to be at most the number of characters specified | ||
* | ||
* Type: `Integer` or `Object` | ||
* Types: `Integer` or `Object` | ||
* | ||
@@ -51,3 +51,3 @@ * Values: | ||
typeof maximumLineLength.value === 'number', | ||
'maximumLineLength option requires the "value" property to be defined' | ||
this.getOptionName() + ' option requires the "value" property to be defined' | ||
); | ||
@@ -68,3 +68,3 @@ | ||
typeof maximumLineLength === 'number', | ||
'maximumLineLength option requires number value or options object' | ||
this.getOptionName() + ' option requires number value or options object' | ||
); | ||
@@ -99,3 +99,3 @@ | ||
if (this._allowUrlComments) { | ||
file.getComments().forEach(function(comment) { | ||
file.iterateTokensByType(['Line', 'Block'], function(comment) { | ||
for (var i = comment.loc.start.line; i <= comment.loc.end.line; i++) { | ||
@@ -102,0 +102,0 @@ lines[i - 1] = lines[i - 1].replace(/(http|https|ftp):\/\/[^\s$]+/, ''); |
@@ -93,5 +93,8 @@ /** | ||
var colon = file.getNextToken(keyToken); | ||
if (colon.loc.start.column !== maxKeyEndPos + 1) { | ||
errors.add('Alignment required', colon.loc.start); | ||
} | ||
errors.assert.spacesBetween({ | ||
token: keyToken, | ||
nextToken: colon, | ||
exactly: maxKeyEndPos - keyToken.loc.end.column + 1, | ||
message: 'Alignment required' | ||
}); | ||
}); | ||
@@ -98,0 +101,0 @@ }); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -45,6 +45,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireAnonymousFunctions) { | ||
configure: function(options) { | ||
assert( | ||
requireAnonymousFunctions === true, | ||
'requireAnonymousFunctions option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -51,0 +51,0 @@ }, |
/** | ||
* Requires blocks to begin and end with a newline | ||
* | ||
* Type: `Boolean` or `Integer` | ||
* Types: `Boolean` or `Integer` | ||
* | ||
@@ -54,9 +54,9 @@ * Values: `true` validates all non-empty blocks, | ||
configure: function(requireBlocksOnNewline) { | ||
configure: function(options) { | ||
assert( | ||
requireBlocksOnNewline === true || typeof requireBlocksOnNewline === 'number', | ||
'requireBlocksOnNewline option requires the value true or an Integer' | ||
options === true || typeof options === 'number', | ||
this.getOptionName() + ' option requires the value true or an Integer' | ||
); | ||
this._minStatements = requireBlocksOnNewline === true ? 0 : requireBlocksOnNewline; | ||
this._minStatements = options === true ? 0 : options; | ||
}, | ||
@@ -79,5 +79,7 @@ | ||
if (openingBracket.loc.start.line === nextToken.loc.start.line) { | ||
errors.add('Missing newline after opening curly brace', openingBracket.loc.end); | ||
} | ||
errors.assert.differentLine({ | ||
token: openingBracket, | ||
nextToken: nextToken, | ||
message: 'Missing newline after opening curly brace' | ||
}); | ||
@@ -87,5 +89,7 @@ var closingBracket = file.getLastNodeToken(node); | ||
if (closingBracket.loc.start.line === prevToken.loc.start.line) { | ||
errors.add('Missing newline before closing curly brace', prevToken.loc.end); | ||
} | ||
errors.assert.differentLine({ | ||
token: prevToken, | ||
nextToken: closingBracket, | ||
message: 'Missing newline before closing curly brace' | ||
}); | ||
}); | ||
@@ -92,0 +96,0 @@ } |
/** | ||
* Requires identifiers to be camelCased or UPPERCASE_WITH_UNDERSCORES | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -62,9 +62,9 @@ * Values: `true` or `"ignoreProperties"` | ||
configure: function(requireCamelCase) { | ||
configure: function(options) { | ||
assert( | ||
requireCamelCase === true || requireCamelCase === 'ignoreProperties', | ||
'requireCamelCaseOrUpperCaseIdentifiers option requires true value or `ignoreProperties` string' | ||
options === true || options === 'ignoreProperties', | ||
this.getOptionName() + ' option requires a true value or `ignoreProperties`' | ||
); | ||
this._ignoreProperties = (requireCamelCase === 'ignoreProperties'); | ||
this._ignoreProperties = (options === 'ignoreProperties'); | ||
}, | ||
@@ -71,0 +71,0 @@ |
/** | ||
* Requires the first alphabetical character of a comment to be uppercase, unless it is part of a multi-line textblock. | ||
* | ||
* Type: `Boolean` | ||
* By default, the prefix for inline comments `jscs` is ignored. | ||
* | ||
* Value: `true` | ||
* Types: `Boolean` or `Object` | ||
* | ||
* Values: | ||
* - `true` | ||
* - `Object`: | ||
* - `allExcept`: array of quoted exceptions | ||
* | ||
* #### Example | ||
@@ -48,2 +53,40 @@ * | ||
* ``` | ||
* | ||
* ```js | ||
* "requireCapitalizedComments": { allExcept: ["jshint"] } | ||
* ``` | ||
* | ||
* Valid: | ||
* | ||
* ``` | ||
* function sayHello() { | ||
* \/* jshint: -W071 *\/ | ||
* | ||
* // I can now say hello in lots of statements, if I like. | ||
* return "Hello"; | ||
* } | ||
* ``` | ||
* | ||
* * Invalid: | ||
* | ||
* ``` | ||
* function sayHello() { | ||
* \/* jshint: -W071 *\/ | ||
* | ||
* // i can now say hello in lots of statements, if I like. | ||
* return "Hello"; | ||
* } | ||
* ``` | ||
* | ||
* * Invalid: | ||
* | ||
* ``` | ||
* function sayHello() { | ||
* \/* istanbul ignore next *\/ | ||
* | ||
* // I'd like to ignore this statement in coverage reports. | ||
* return "Hello"; | ||
* } | ||
* ``` | ||
* | ||
*/ | ||
@@ -56,7 +99,34 @@ | ||
module.exports.prototype = { | ||
configure: function(requireCapitalizedComments) { | ||
configure: function(options) { | ||
// except comments that begin with `jscs`, since these are used to | ||
// selectively enable/disable rules within a file | ||
this._exceptions = { | ||
'jscs': true | ||
}; | ||
var optionName = this.getOptionName(); | ||
var isObject = typeof options === 'object'; | ||
assert( | ||
requireCapitalizedComments === true, | ||
'requireCapitalizedComments option requires a value of true or should be removed' | ||
options === true || | ||
isObject, | ||
optionName + ' option requires a true value ' + | ||
'or an object with String[] `allExcept` property' | ||
); | ||
if (isObject) { | ||
var exceptions = options.allExcept; | ||
// verify items in `allExcept` property in object are string values | ||
assert( | ||
Array.isArray(exceptions) && | ||
exceptions.every(function(el) { return typeof el === 'string'; }), | ||
'Property `allExcept` in ' + optionName + ' should be an array of strings' | ||
); | ||
for (var i = 0, l = exceptions.length; i < l; i++) { | ||
this._exceptions[exceptions[i]] = true; | ||
} | ||
} | ||
}, | ||
@@ -70,2 +140,3 @@ | ||
var inTextBlock = null; | ||
var exceptions = this._exceptions; | ||
@@ -75,3 +146,11 @@ var letterPattern = require('../../patterns/L'); | ||
file.getComments().forEach(function(comment) { | ||
file.iterateTokensByType(['Line', 'Block'], function(comment) { | ||
// strip leading whitespace and any asterisks | ||
// split on whitespace and colons | ||
var splitComment = comment.value.replace(/(^\s+|[\*])/g, '').split(/[\s\:]/g); | ||
if (exceptions[splitComment[0]]) { | ||
return; | ||
} | ||
var stripped = comment.value.replace(/[\n\s\*]/g, ''); | ||
@@ -78,0 +157,0 @@ var firstChar = stripped[0]; |
/** | ||
* Requires constructors to be capitalized (except for `this`) | ||
* | ||
* Type: `Boolean` or `Object` | ||
* Types: `Boolean` or `Object` | ||
* | ||
@@ -39,10 +39,10 @@ * Values: `true` or Object with `allExcept` Array of quoted identifiers which are exempted | ||
module.exports.prototype = { | ||
configure: function(requireCapitalizedConstructors) { | ||
configure: function(options) { | ||
assert( | ||
requireCapitalizedConstructors === true || Array.isArray(requireCapitalizedConstructors.allExcept), | ||
'requireCapitalizedConstructors option requires object of exceptions or true value' | ||
options === true || Array.isArray(options.allExcept), | ||
this.getOptionName() + ' option requires a true value or an object of exceptions' | ||
); | ||
this._allowedConstructors = {}; | ||
var allExcept = requireCapitalizedConstructors.allExcept; | ||
var allExcept = options.allExcept; | ||
if (allExcept) { | ||
@@ -49,0 +49,0 @@ for (var i = 0, l = allExcept.length; i < l; i++) { |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -43,11 +43,7 @@ * JSHint: [`laxcomma`](http://www.jshint.com/docs/options/#laxcomma) | ||
configure: function(requireCommaBeforeLineBreak) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireCommaBeforeLineBreak === 'boolean', | ||
'requireCommaBeforeLineBreak option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireCommaBeforeLineBreak === true, | ||
'requireCommaBeforeLineBreak option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -60,10 +56,8 @@ | ||
check: function(file, errors) { | ||
file.iterateTokensByType('Punctuator', function(token) { | ||
if (token.value === ',') { | ||
errors.assert.sameLine({ | ||
token: file.getPrevToken(token), | ||
nextToken: token, | ||
message: 'Commas should not be placed on new line' | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Punctuator', ',', function(token) { | ||
errors.assert.sameLine({ | ||
token: file.getPrevToken(token), | ||
nextToken: token, | ||
message: 'Commas should not be placed on new line' | ||
}); | ||
}); | ||
@@ -70,0 +64,0 @@ } |
/** | ||
* Requires curly braces after statements. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -51,3 +51,3 @@ * Values: Array of quoted keywords or `true` to require curly braces after the following keywords: | ||
Array.isArray(statementTypes) || statementTypes === true, | ||
'requireCurlyBraces option requires array or true value' | ||
this.getOptionName() + ' option requires array or true value' | ||
); | ||
@@ -54,0 +54,0 @@ |
/** | ||
* Requires member expressions to use dot notation when possible | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -83,8 +83,8 @@ * Values: | ||
configure: function(requireDotNotation) { | ||
configure: function(options) { | ||
assert( | ||
requireDotNotation === true || requireDotNotation === 'except_snake_case', | ||
'requireDotNotation option requires true value or "except_snake_case" string' | ||
options === true || options === 'except_snake_case', | ||
this.getOptionName() + ' option requires a true value or "except_snake_case"' | ||
); | ||
this._exceptSnakeCase = requireDotNotation === 'except_snake_case'; | ||
this._exceptSnakeCase = options === 'except_snake_case'; | ||
}, | ||
@@ -91,0 +91,0 @@ |
@@ -11,3 +11,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -60,6 +60,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireFunctionDeclarations) { | ||
configure: function(options) { | ||
assert( | ||
requireFunctionDeclarations === true, | ||
'requireFunctionDeclarations option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -66,0 +66,0 @@ }, |
@@ -43,7 +43,4 @@ /** | ||
configure: function(keywords) { | ||
assert(Array.isArray(keywords), 'requireKeywordsOnNewLine option requires array value'); | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
assert(Array.isArray(keywords), this.getOptionName() + ' option requires array value'); | ||
this._keywords = keywords; | ||
}, | ||
@@ -56,11 +53,7 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByType('Keyword', function(token) { | ||
if (keywordIndex[token.value]) { | ||
errors.assert.differentLine({ | ||
token: file.getPrevToken(token), | ||
nextToken: token | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
errors.assert.differentLine({ | ||
token: file.getPrevToken(token), | ||
nextToken: token | ||
}); | ||
}); | ||
@@ -67,0 +60,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -47,11 +47,7 @@ * #### Example | ||
configure: function(requireLineBreakAfterVariableAssignment) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireLineBreakAfterVariableAssignment === 'boolean', | ||
'requireLineFeedAtFileEnd option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireLineBreakAfterVariableAssignment === true, | ||
'requireLineFeedAtFileEnd option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -65,15 +61,20 @@ | ||
var lastDeclaration; | ||
file.iterate(function(node) { | ||
file.iterateNodesByType('VariableDeclaration', function(node) { | ||
if (node.parentNode.type === 'ForStatement' || | ||
node.parentNode.type === 'ForInStatement' || | ||
node.parentNode.type === 'ForOfStatement') { | ||
return; | ||
} | ||
if (node && node.type === 'VariableDeclaration') { | ||
for (var i = 0; i < node.declarations.length; i++) { | ||
var thisDeclaration = node.declarations[i]; | ||
if (thisDeclaration.parentNode.kind === 'var') { | ||
if (lastDeclaration && lastDeclaration.init && | ||
thisDeclaration.loc.start.line === lastDeclaration.loc.end.line) { | ||
errors.add('Variable assignments should be followed by new line', | ||
thisDeclaration.loc.start.line, thisDeclaration.loc.start.column); | ||
} | ||
lastDeclaration = thisDeclaration; | ||
for (var i = 0; i < node.declarations.length; i++) { | ||
var thisDeclaration = node.declarations[i]; | ||
if (thisDeclaration.parentNode.kind === 'var') { | ||
if (lastDeclaration && lastDeclaration.init) { | ||
errors.assert.differentLine({ | ||
token: lastDeclaration, | ||
nextToken: thisDeclaration, | ||
message: 'Variable assignments should be followed by new line' | ||
}); | ||
} | ||
lastDeclaration = thisDeclaration; | ||
} | ||
@@ -80,0 +81,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -22,11 +22,7 @@ * #### Example | ||
configure: function(requireLineFeedAtFileEnd) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireLineFeedAtFileEnd === 'boolean', | ||
'requireLineFeedAtFileEnd option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireLineFeedAtFileEnd === true, | ||
'requireLineFeedAtFileEnd option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -39,8 +35,11 @@ | ||
check: function(file, errors) { | ||
var lines = file.getLines(); | ||
if (lines[lines.length - 1] !== '') { | ||
errors.add('Missing line feed at file end', lines.length, 0); | ||
} | ||
var lastToken = file.getLastToken(); | ||
var prevToken = file.getPrevToken(lastToken, {includeComments: true}); | ||
errors.assert.differentLine({ | ||
token: prevToken, | ||
nextToken: lastToken, | ||
message: 'Missing line feed at file end' | ||
}); | ||
} | ||
}; |
/** | ||
* Requires multiple `var` declaration. | ||
* | ||
* Type: `Boolean` or `String` | ||
* Types: `Boolean` or `String` | ||
* | ||
@@ -100,14 +100,9 @@ * Values: `true` or `"onevar"` | ||
module.exports.prototype = { | ||
configure: function(requireMultipleVarDecl) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireMultipleVarDecl === 'boolean' || | ||
typeof requireMultipleVarDecl === 'string', | ||
'requireMultipleVarDecl option requires boolean or string' | ||
options === true || options === 'onevar', | ||
this.getOptionName() + ' option requires a true value or `onevar`' | ||
); | ||
assert( | ||
requireMultipleVarDecl === true || requireMultipleVarDecl === 'onevar', | ||
'requireMultipleVarDecl option requires true value or `onevar` string' | ||
); | ||
this._check = typeof requireMultipleVarDecl === 'string' ? onevar : consecutive; | ||
this._check = typeof options === 'string' ? onevar : consecutive; | ||
}, | ||
@@ -114,0 +109,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -78,11 +78,7 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireNewlineBeforeBlockStatements) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireNewlineBeforeBlockStatements === 'boolean', | ||
'requireNewlineBeforeBlockStatements option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireNewlineBeforeBlockStatements === true, | ||
'requireNewlineBeforeBlockStatements option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -89,0 +85,0 @@ |
/** | ||
* Requires operators to appear before line breaks and not after. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -61,3 +61,3 @@ * Values: Array of quoted operators or `true` to require all possible binary operators to appear before line breaks | ||
Array.isArray(operators) || isTrue, | ||
'requireOperatorBeforeLineBreak option requires array value or true value' | ||
this.getOptionName() + ' option requires array value or true value' | ||
); | ||
@@ -64,0 +64,0 @@ |
/** | ||
* Requires an empty line above the specified keywords unless the keyword is the first expression in a block. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -73,3 +73,3 @@ * Values: Array of quoted types or `true` to require padding new lines before all of the keywords below. | ||
assert(Array.isArray(keywords) || keywords === true, | ||
'requirePaddingNewlinesBeforeKeywords option requires array or true value'); | ||
this.getOptionName() + ' option requires array or true value'); | ||
@@ -80,6 +80,3 @@ if (keywords === true) { | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
this._keywords = keywords; | ||
}, | ||
@@ -93,29 +90,27 @@ | ||
var excludedTokens = [':', ',', '(', '=']; | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByType('Keyword', function(token) { | ||
if (keywordIndex[token.value]) { | ||
var prevToken = file.getPrevToken(token); | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
var prevToken = file.getPrevToken(token); | ||
// Handle special case of 'else if' construct. | ||
if (token.value === 'if' && prevToken && prevToken.value === 'else') { | ||
return; | ||
// Handling for special cases. | ||
} else if (prevToken && excludedTokens.indexOf(prevToken.value) > -1) { | ||
return; | ||
} | ||
// Handle special case of 'else if' construct. | ||
if (token.value === 'if' && prevToken && prevToken.value === 'else') { | ||
return; | ||
// Handling for special cases. | ||
} else if (prevToken && excludedTokens.indexOf(prevToken.value) > -1) { | ||
return; | ||
} | ||
// Handle all other cases | ||
// The { character is there to handle the case of a matching token which happens to be the first | ||
// statement in a block | ||
// The ) character is there to handle the case of `if (...) matchingKeyword` in which case | ||
// requiring padding would break the statement | ||
if (prevToken && prevToken.value !== '{' && prevToken.value !== ')' && | ||
token.loc.start.line - prevToken.loc.end.line < 2) { | ||
errors.add( | ||
'Keyword `' + token.value + '` should have an empty line above it', | ||
token.loc.start.line, | ||
token.loc.start.column | ||
); | ||
} | ||
// Handle all other cases | ||
// The { character is there to handle the case of a matching token which happens to be the first | ||
// statement in a block | ||
// The ) character is there to handle the case of `if (...) matchingKeyword` in which case | ||
// requiring padding would break the statement | ||
if (prevToken && prevToken.value !== '{' && prevToken.value !== ')') { | ||
errors.assert.linesBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
atLeast: 2, | ||
message: 'Keyword `' + token.value + '` should have an empty line above it' | ||
}); | ||
} | ||
@@ -122,0 +117,0 @@ }); |
/** | ||
* Requires blocks to begin and end with 2 newlines | ||
* | ||
* Type: `Boolean` or `Integer` | ||
* Types: `Boolean` or `Integer` | ||
* | ||
@@ -68,9 +68,9 @@ * Values: `true` validates all non-empty blocks, | ||
configure: function(requirePaddingNewlinesInBlocks) { | ||
configure: function(options) { | ||
assert( | ||
requirePaddingNewlinesInBlocks === true || typeof requirePaddingNewlinesInBlocks === 'number', | ||
'requirePaddingNewlinesInBlocks option requires the value true or an Integer' | ||
options === true || typeof options === 'number', | ||
this.getOptionName() + ' option requires the value true or an Integer' | ||
); | ||
this._minStatements = requirePaddingNewlinesInBlocks === true ? 0 : requirePaddingNewlinesInBlocks; | ||
this._minStatements = options === true ? 0 : options; | ||
}, | ||
@@ -91,14 +91,18 @@ | ||
var openingBracket = file.getFirstNodeToken(node); | ||
var nextToken = file.getCommentAfterToken(openingBracket) || file.getNextToken(openingBracket); | ||
if (nextToken.loc.start.line - openingBracket.loc.start.line < 2) { | ||
errors.add('Expected a padding newline after opening curly brace', openingBracket.loc.end); | ||
} | ||
errors.assert.linesBetween({ | ||
token: openingBracket, | ||
nextToken: file.getNextToken(openingBracket, {includeComments: true}), | ||
atLeast: 2, | ||
message: 'Expected a padding newline after opening curly brace' | ||
}); | ||
var closingBracket = file.getLastNodeToken(node); | ||
var prevToken = file.getCommentBeforeToken(closingBracket) || file.getPrevToken(closingBracket); | ||
if (closingBracket.loc.start.line - prevToken.loc.start.line < 2) { | ||
errors.add('Expected a padding newline before closing curly brace', prevToken.loc.end); | ||
} | ||
errors.assert.linesBetween({ | ||
token: file.getPrevToken(closingBracket, {includeComments: true}), | ||
nextToken: closingBracket, | ||
atLeast: 2, | ||
message: 'Expected a padding newline before closing curly brace' | ||
}); | ||
}); | ||
@@ -105,0 +109,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -44,8 +44,4 @@ * #### Example | ||
assert( | ||
typeof value === 'boolean', | ||
'requirePaddingNewLinesInObjects option requires boolean value' | ||
); | ||
assert( | ||
value === true, | ||
'requirePaddingNewLinesInObjects option requires true value or should be removed' | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -67,12 +63,15 @@ }, | ||
if (openingBracket.loc.end.line === nextToken.loc.start.line) { | ||
errors.add('Missing newline after opening curly brace', nextToken.loc.start); | ||
} | ||
errors.assert.differentLine({ | ||
token: openingBracket, | ||
nextToken: nextToken, | ||
message: 'Missing newline after opening curly brace' | ||
}); | ||
var closingBracket = file.getLastNodeToken(node); | ||
var prevToken = file.getPrevToken(closingBracket); | ||
if (closingBracket.loc.start.line === prevToken.loc.end.line) { | ||
errors.add('Missing newline before closing curly brace', closingBracket.loc.start); | ||
} | ||
errors.assert.differentLine({ | ||
token: file.getPrevToken(closingBracket), | ||
nextToken: closingBracket, | ||
message: 'Missing newline before closing curly brace' | ||
}); | ||
}); | ||
@@ -79,0 +78,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -44,11 +44,7 @@ * JSHint: [`immed`](http://www.jshint.com/docs/options/#immed) | ||
configure: function(requireParenthesesAroundIIFE) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireParenthesesAroundIIFE === 'boolean', | ||
'requireParenthesesAroundIIFE option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireParenthesesAroundIIFE === true, | ||
'requireParenthesesAroundIIFE option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -55,0 +51,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -12,3 +12,3 @@ * #### Example | ||
* ```js | ||
* "requiresQuotedKeysInObjects": true | ||
* "requireQuotedKeysInObjects": true | ||
* ``` | ||
@@ -35,6 +35,6 @@ * | ||
configure: function(requireQuotedKeysInObjects) { | ||
configure: function(options) { | ||
assert( | ||
requireQuotedKeysInObjects === true, | ||
this.getOptionName() + ' option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -41,0 +41,0 @@ }, |
/** | ||
* Disallows sticking binary operators to the right. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -51,3 +51,3 @@ * Values: Array of quoted operators or `true` to require space after all possible binary operators | ||
Array.isArray(operators) || isTrue, | ||
'requireSpaceAfterBinaryOperators option requires array or true value' | ||
this.getOptionName() + ' option requires array or true value' | ||
); | ||
@@ -74,10 +74,8 @@ | ||
if (operators[',']) { | ||
file.iterateTokensByType('Punctuator', function(token) { | ||
if (token.value === ',') { | ||
errors.assert.whitespaceBetween({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Operator , should not stick to following expression' | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Punctuator', ',', function(token) { | ||
errors.assert.whitespaceBetween({ | ||
token: token, | ||
nextToken: file.getNextToken(token), | ||
message: 'Operator , should not stick to following expression' | ||
}); | ||
}); | ||
@@ -84,0 +82,0 @@ } |
/** | ||
* Requires space after keyword. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -45,7 +45,2 @@ * Values: Array of quoted keywords or `true` to require all of the keywords below to have a space afterward. | ||
var assert = require('assert'); | ||
var util = require('util'); | ||
var texts = [ | ||
'Missing space after "%s" keyword', | ||
'Should be one space instead of %d, after "%s" keyword' | ||
]; | ||
@@ -61,3 +56,3 @@ var defaultKeywords = require('../utils').spacedKeywords; | ||
Array.isArray(keywords) || keywords === true, | ||
'requireSpaceAfterKeywords option requires array or true value'); | ||
this.getOptionName() + ' option requires array or true value'); | ||
@@ -68,6 +63,3 @@ if (keywords === true) { | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
this._keywords = keywords; | ||
}, | ||
@@ -80,25 +72,15 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
var nextToken = file.getNextToken(token, {includeComments: true}); | ||
file.iterateTokensByType(['Keyword'], function(token) { | ||
if (keywordIndex[token.value]) { | ||
var nextToken = file.getCommentAfterToken(token) || file.getNextToken(token); | ||
var diff = nextToken.range[0] - token.range[1]; | ||
if (nextToken.type === 'Punctuator' && nextToken.value === ';') { | ||
return; | ||
} | ||
if (nextToken.loc.end.line === token.loc.start.line && | ||
diff !== 1 | ||
) { | ||
if (nextToken.type !== 'Punctuator' || nextToken.value !== ';') { | ||
errors.add( | ||
util.format.apply(null, | ||
diff === 0 ? | ||
[texts[0], token.value] : | ||
[texts[1], diff, token.value] | ||
), | ||
nextToken.loc.start.line, | ||
nextToken.loc.start.column | ||
); | ||
} | ||
} | ||
} | ||
errors.assert.spacesBetween({ | ||
token: token, | ||
nextToken: nextToken, | ||
exactly: 1, | ||
message: 'One space required after "' + token.value + '" keyword' | ||
}); | ||
}); | ||
@@ -105,0 +87,0 @@ } |
/** | ||
* Requires that a line comment (`//`) be followed by a space. | ||
* | ||
* Type: `Boolean` or `Object` or `String` | ||
* Types: `Boolean`, `Object` or `String` | ||
* | ||
* Values: | ||
* - `true` | ||
* - `"allowSlash"` (*deprecated* use `"except": ["/"]`) allows `/// ` format | ||
* - `"allowSlash"` (*deprecated* use `"allExcept": ["/"]`) allows `/// ` format | ||
* - `Object`: | ||
@@ -40,8 +40,8 @@ * - `allExcept`: array of allowed strings before space `//(here) ` | ||
configure: function(requireSpaceAfterLineComment) { | ||
configure: function(options) { | ||
assert( | ||
requireSpaceAfterLineComment === true || | ||
requireSpaceAfterLineComment === 'allowSlash' || | ||
typeof requireSpaceAfterLineComment === 'object', | ||
'requireSpaceAfterLineComment option requires the value `true` ' + | ||
options === true || | ||
options === 'allowSlash' || | ||
typeof options === 'object', | ||
this.getOptionName() + ' option requires a true value ' + | ||
'or an object with String[] `allExcept` property' | ||
@@ -52,6 +52,6 @@ ); | ||
assert( | ||
typeof requireSpaceAfterLineComment !== 'object' || | ||
Array.isArray(requireSpaceAfterLineComment.allExcept) && | ||
typeof requireSpaceAfterLineComment.allExcept[0] === 'string', | ||
'Property `allExcept` in requireSpaceAfterLineComment should be an array of strings' | ||
typeof options !== 'object' || | ||
Array.isArray(options.allExcept) && | ||
typeof options.allExcept[0] === 'string', | ||
'Property `allExcept` in ' + this.getOptionName() + ' should be an array of strings' | ||
); | ||
@@ -62,4 +62,4 @@ | ||
// need to drop allowSlash support in 2.0. Fixes #697 | ||
this._allExcept = requireSpaceAfterLineComment === 'allowSlash' ? ['/'] : | ||
requireSpaceAfterLineComment.allExcept || []; | ||
this._allExcept = options === 'allowSlash' ? ['/'] : | ||
options.allExcept || []; | ||
}, | ||
@@ -72,26 +72,23 @@ | ||
check: function(file, errors) { | ||
var comments = file.getComments(); | ||
var allExcept = this._allExcept; | ||
comments.forEach(function(comment) { | ||
if (comment.type === 'Line') { | ||
var value = comment.value; | ||
file.iterateTokensByType('Line', function(comment) { | ||
var value = comment.value; | ||
// cutout exceptions | ||
allExcept.forEach(function(el) { | ||
if (value.indexOf(el) === 0) { | ||
value = value.substr(el.length); | ||
} | ||
}); | ||
if (value.length === 0) { | ||
return; | ||
// cutout exceptions | ||
allExcept.forEach(function(el) { | ||
if (value.indexOf(el) === 0) { | ||
value = value.substr(el.length); | ||
} | ||
}); | ||
if (value[0] !== ' ') { | ||
errors.add('Missing space after line comment', comment.loc.start); | ||
} | ||
if (value.length === 0) { | ||
return; | ||
} | ||
if (value[0] !== ' ') { | ||
errors.add('Missing space after line comment', comment.loc.start); | ||
} | ||
}); | ||
} | ||
}; |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -31,6 +31,6 @@ * #### Example | ||
configure: function(requireSpaceAfterObjectKeys) { | ||
configure: function(options) { | ||
assert( | ||
requireSpaceAfterObjectKeys === true, | ||
this.getOptionName() + ' option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -37,0 +37,0 @@ }, |
/** | ||
* Disallows sticking unary operators to the right. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -6,0 +6,0 @@ * Values: Array of quoted operators or `true` to require space after prefix for all unary operators |
/** | ||
* Disallows sticking binary operators to the left. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -55,3 +55,3 @@ * Values: Array of quoted operators or `true` to require space before all possible binary operators | ||
Array.isArray(operators) || isTrue, | ||
'requireSpaceBeforeBinaryOperators option requires array or true value' | ||
this.getOptionName() + ' option requires array or true value' | ||
); | ||
@@ -78,10 +78,8 @@ | ||
if (operators[',']) { | ||
file.iterateTokensByType('Punctuator', function(token) { | ||
if (token.value === ',') { | ||
errors.assert.whitespaceBetween({ | ||
token: file.getPrevToken(token), | ||
nextToken: token, | ||
message: 'Operator , should not stick to preceding expression' | ||
}); | ||
} | ||
file.iterateTokensByTypeAndValue('Punctuator', ',', function(token) { | ||
errors.assert.whitespaceBetween({ | ||
token: file.getPrevToken(token), | ||
nextToken: token, | ||
message: 'Operator , should not stick to preceding expression' | ||
}); | ||
}); | ||
@@ -88,0 +86,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -57,11 +57,7 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireSpaceBeforeBlockStatements) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireSpaceBeforeBlockStatements === 'boolean', | ||
'requireSpaceBeforeBlockStatements option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireSpaceBeforeBlockStatements === true, | ||
'requireSpaceBeforeBlockStatements option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -77,6 +73,6 @@ | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: file.getPrevToken(first), | ||
nextToken: first, | ||
spaces: 1, | ||
exactly: 1, | ||
disallowNewLine: true, | ||
@@ -83,0 +79,0 @@ message: 'One space required before opening brace for block expressions' |
/** | ||
* Requires space before keyword. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -36,7 +36,2 @@ * Values: Array of quoted keywords or `true` to require all possible keywords to have a preceding space. | ||
var assert = require('assert'); | ||
var util = require('util'); | ||
var texts = [ | ||
'Missing space before "%s" keyword', | ||
'Should be one space instead of %d, before "%s" keyword' | ||
]; | ||
@@ -52,3 +47,3 @@ var defaultKeywords = require('../utils').spacedKeywords; | ||
Array.isArray(keywords) || keywords === true, | ||
'requireSpaceAfterKeywords option requires array or true value'); | ||
this.getOptionName() + ' option requires array or true value'); | ||
@@ -59,6 +54,3 @@ if (keywords === true) { | ||
this._keywordIndex = {}; | ||
for (var i = 0, l = keywords.length; i < l; i++) { | ||
this._keywordIndex[keywords[i]] = true; | ||
} | ||
this._keywords = keywords; | ||
}, | ||
@@ -71,28 +63,14 @@ | ||
check: function(file, errors) { | ||
var keywordIndex = this._keywordIndex; | ||
file.iterateTokensByTypeAndValue('Keyword', this._keywords, function(token) { | ||
var prevToken = file.getPrevToken(token, {includeComments: true}); | ||
if (!prevToken || prevToken.isComment) { | ||
return; | ||
} | ||
file.iterateTokensByType(['Keyword'], function(token) { | ||
if (keywordIndex[token.value]) { | ||
var prevToken = file.getPrevToken(token); | ||
if (!prevToken) { | ||
return; | ||
} | ||
prevToken = file.getCommentBeforeToken(token) || prevToken; | ||
var diff = token.range[0] - prevToken.range[1]; | ||
if (prevToken.loc.end.line === token.loc.start.line && diff !== 1) { | ||
if (prevToken.type !== 'Punctuator' || prevToken.value !== ';') { | ||
errors.add( | ||
util.format.apply(null, | ||
diff === 0 ? | ||
[texts[0], token.value] : | ||
[texts[1], diff, token.value] | ||
), | ||
token.loc.start.line, | ||
token.loc.start.column | ||
); | ||
} | ||
} | ||
if (prevToken.type !== 'Punctuator' || prevToken.value !== ';') { | ||
errors.assert.whitespaceBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
message: 'Missing space before "' + token.value + '" keyword' | ||
}); | ||
} | ||
@@ -99,0 +77,0 @@ }); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -31,6 +31,6 @@ * #### Example | ||
configure: function(disallow) { | ||
configure: function(options) { | ||
assert( | ||
disallow === true, | ||
this.getOptionName() + ' option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -37,0 +37,0 @@ }, |
/** | ||
* Disallows sticking unary operators to the left. | ||
* | ||
* Type: `Array` or `Boolean` | ||
* Types: `Array` or `Boolean` | ||
* | ||
@@ -6,0 +6,0 @@ * Values: Array of quoted operators or `true` to require space before postfix for all unary operators |
@@ -33,11 +33,7 @@ /** | ||
configure: function(requireSpaceBetweenArguments) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireSpaceBetweenArguments === 'boolean', | ||
this.getOptionName() + ' option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireSpaceBetweenArguments === true, | ||
this.getOptionName() + ' option requires true value or should be removed' | ||
); | ||
}, | ||
@@ -44,0 +40,0 @@ |
@@ -47,3 +47,3 @@ /** | ||
typeof options === 'object', | ||
'requireSpacesInAnonymousFunctionExpression option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -54,3 +54,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'requireSpacesInAnonymousFunctionExpression.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -63,3 +63,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'requireSpacesInAnonymousFunctionExpression.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -71,3 +71,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'requireSpacesInAnonymousFunctionExpression must have beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace ' + | ||
' or beforeOpeningRoundBrace property' | ||
@@ -74,0 +74,0 @@ ); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -33,6 +33,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireSpacesInCallExpression) { | ||
configure: function(options) { | ||
assert( | ||
requireSpacesInCallExpression === true, | ||
'requireSpacesInCallExpression option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -39,0 +39,0 @@ }, |
/** | ||
* Requires space before and/or after `?` or `:` in conditional expressions. | ||
* | ||
* Type: `Object` or `Boolean` | ||
* Types: `Object` or `Boolean` | ||
* | ||
@@ -62,3 +62,3 @@ * Values: `"afterTest"`, `"beforeConsequent"`, `"afterConsequent"`, `"beforeAlternate"` as child properties, | ||
typeof options === 'object', | ||
optionName + ' option must be an object or boolean true' | ||
optionName + ' option requires a true value or an object' | ||
); | ||
@@ -95,3 +95,2 @@ | ||
file.iterateNodesByType(['ConditionalExpression'], function(node) { | ||
var test = node.test; | ||
var consequent = node.consequent; | ||
@@ -107,8 +106,7 @@ var alternate = node.alternate; | ||
token = file.getPrevToken(questionMarkToken); | ||
if (token.loc.end.column === questionMarkToken.loc.start.column) { | ||
errors.add( | ||
'Missing space after test', | ||
test.loc.end | ||
); | ||
} | ||
errors.assert.whitespaceBetween({ | ||
token: token, | ||
nextToken: questionMarkToken, | ||
message: 'Missing space after test' | ||
}); | ||
} | ||
@@ -118,8 +116,7 @@ | ||
token = file.getNextToken(questionMarkToken); | ||
if (token.loc.start.column === questionMarkToken.loc.end.column) { | ||
errors.add( | ||
'Missing space before consequent', | ||
consequent.loc.start | ||
); | ||
} | ||
errors.assert.whitespaceBetween({ | ||
token: questionMarkToken, | ||
nextToken: token, | ||
message: 'Missing space before consequent' | ||
}); | ||
} | ||
@@ -129,8 +126,7 @@ | ||
token = file.getPrevToken(colonToken); | ||
if (token.loc.end.column === colonToken.loc.start.column) { | ||
errors.add( | ||
'Missing space after consequent', | ||
consequent.loc.end | ||
); | ||
} | ||
errors.assert.whitespaceBetween({ | ||
token: token, | ||
nextToken: colonToken, | ||
message: 'Missing space after consequent' | ||
}); | ||
} | ||
@@ -140,8 +136,7 @@ | ||
token = file.getNextToken(colonToken); | ||
if (token.loc.start.column === colonToken.loc.end.column) { | ||
errors.add( | ||
'Missing space before alternate', | ||
alternate.loc.start | ||
); | ||
} | ||
errors.assert.whitespaceBetween({ | ||
token: colonToken, | ||
nextToken: token, | ||
message: 'Missing space before alternate' | ||
}); | ||
} | ||
@@ -148,0 +143,0 @@ }.bind(this)); |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` to requires spaces inbetween for statement. | ||
* Value: `true` to requires spaces inbetween for statement. | ||
* | ||
@@ -49,6 +49,6 @@ * #### Example | ||
module.exports.prototype = { | ||
configure: function(requireSpacesInForStatement) { | ||
configure: function(options) { | ||
assert( | ||
requireSpacesInForStatement === true, | ||
'requireSpacesInForStatement option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -65,6 +65,6 @@ }, | ||
var testToken = file.getFirstNodeToken(node.test); | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: file.getPrevToken(testToken), | ||
nextToken: testToken, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required after semicolon' | ||
@@ -75,6 +75,6 @@ }); | ||
var updateToken = file.getFirstNodeToken(node.update); | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: file.getPrevToken(updateToken), | ||
nextToken: updateToken, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required after semicolon' | ||
@@ -81,0 +81,0 @@ }); |
@@ -29,2 +29,3 @@ /** | ||
* function a (){} | ||
* function a(){} | ||
* ``` | ||
@@ -41,3 +42,3 @@ */ | ||
typeof options === 'object', | ||
'requireSpacesInFunctionDeclaration option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -48,3 +49,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'requireSpacesInFunctionDeclaration.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -57,3 +58,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'requireSpacesInFunctionDeclaration.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -65,3 +66,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'requireSpacesInFunctionDeclaration must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
); | ||
@@ -68,0 +69,0 @@ |
@@ -30,2 +30,6 @@ /** | ||
* var x = function() {}; | ||
* var x = function (){}; | ||
* var x = function(){}; | ||
* var x = function a() {}; | ||
* var x = function a (){}; | ||
* var x = function a(){}; | ||
@@ -43,3 +47,3 @@ * ``` | ||
typeof options === 'object', | ||
'requireSpacesInFunctionExpression option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -50,3 +54,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'requireSpacesInFunctionExpression.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -59,3 +63,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'requireSpacesInFunctionExpression.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -67,3 +71,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'requireSpacesInFunctionExpression must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
); | ||
@@ -70,0 +74,0 @@ |
@@ -5,3 +5,3 @@ /** | ||
* Requires space before `()` or `{}` in function expressions (both [named](#requirespacesinnamedfunctionexpression) | ||
* and [anonymous](#requirespacesinanonymousfunctionexpression)). | ||
* and [anonymous](#requirespacesinanonymousfunctionexpression)) and function declarations. | ||
* | ||
@@ -16,3 +16,3 @@ * Type: `Object` | ||
* ```js | ||
* "requireSpacesInFunctionExpression": { | ||
* "requireSpacesInFunction": { | ||
* "beforeOpeningRoundBrace": true, | ||
@@ -28,2 +28,3 @@ * "beforeOpeningCurlyBrace": true | ||
* var x = function a () {}; | ||
* function a () {} | ||
* ``` | ||
@@ -35,3 +36,10 @@ * | ||
* var x = function() {}; | ||
* var x = function (){}; | ||
* var x = function(){}; | ||
* var x = function a() {}; | ||
* var x = function a (){}; | ||
* var x = function a(){}; | ||
* function a() {} | ||
* function a (){} | ||
* function a(){} | ||
* ``` | ||
@@ -48,3 +56,3 @@ */ | ||
typeof options === 'object', | ||
'requireSpacesInFunction option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -55,3 +63,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'requireSpacesInFunction.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -64,3 +72,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'requireSpacesInFunction.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -72,3 +80,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'requireSpacesInFunction must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace or beforeOpeningRoundBrace property' | ||
); | ||
@@ -75,0 +83,0 @@ |
@@ -28,2 +28,3 @@ /** | ||
* var x = function a() {}; | ||
* var x = function a (){}; | ||
* var x = function a(){}; | ||
@@ -41,3 +42,3 @@ * ``` | ||
typeof options === 'object', | ||
'requireSpacesInNamedFunctionExpression option must be the object' | ||
this.getOptionName() + ' option must be the object' | ||
); | ||
@@ -48,3 +49,3 @@ | ||
options.beforeOpeningRoundBrace === true, | ||
'requireSpacesInNamedFunctionExpression.beforeOpeningRoundBrace ' + | ||
this.getOptionName() + '.beforeOpeningRoundBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -57,3 +58,3 @@ ); | ||
options.beforeOpeningCurlyBrace === true, | ||
'requireSpacesInNamedFunctionExpression.beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + '.beforeOpeningCurlyBrace ' + | ||
'property requires true value or should be removed' | ||
@@ -65,3 +66,3 @@ ); | ||
options.beforeOpeningCurlyBrace || options.beforeOpeningRoundBrace, | ||
'requireSpacesInNamedFunctionExpression must have beforeOpeningCurlyBrace ' + | ||
this.getOptionName() + ' must have beforeOpeningCurlyBrace ' + | ||
'or beforeOpeningRoundBrace property' | ||
@@ -68,0 +69,0 @@ ); |
/** | ||
* Requires space after opening array square bracket and before closing. | ||
* | ||
* Type: `String` or `Object` | ||
* Types: `String` or `Object` | ||
* | ||
@@ -61,3 +61,3 @@ * Values: `"all"` for strict mode, `"allButNested"` (*deprecated* use `"allExcept": [ "[", "]"]`) | ||
var error = 'requireSpacesInsideArrayBrackets rule' + | ||
var error = this.getOptionName() + ' rule' + | ||
' requires string value "all" or "allButNested" or object'; | ||
@@ -99,5 +99,5 @@ | ||
var openBracket = file.getFirstNodeToken(node); | ||
var afterOpen = file.getNextToken(openBracket); | ||
var afterOpen = file.getNextToken(openBracket, {includeComments: true}); | ||
var closeBracket = file.getLastNodeToken(node); | ||
var beforeClose = file.getPrevToken(closeBracket); | ||
var beforeClose = file.getPrevToken(closeBracket, {includeComments: true}); | ||
@@ -110,6 +110,6 @@ // Skip for empty array brackets | ||
if (!(afterOpen.value in exceptions)) { | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: openBracket, | ||
nextToken: afterOpen, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required after opening bracket' | ||
@@ -120,6 +120,6 @@ }); | ||
if (!(beforeClose.value in exceptions)) { | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: beforeClose, | ||
nextToken: closeBracket, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required before closing bracket' | ||
@@ -126,0 +126,0 @@ }); |
/** | ||
* Requires space after opening square bracket and before closing. | ||
* | ||
* Type: `Boolean` or `Object` | ||
* Types: `Boolean` or `Object` | ||
* | ||
@@ -50,3 +50,3 @@ * Values: `true` for strict mode, or `"allExcept": [ "[", "]"]` | ||
var error = 'requireSpacesInsideBrackets rule requires string value true or object'; | ||
var error = this.getOptionName() + ' rule requires string value true or object'; | ||
@@ -88,6 +88,6 @@ if (isObject) { | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: token, | ||
nextToken: nextToken, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required after opening bracket' | ||
@@ -110,6 +110,6 @@ }); | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required before closing bracket' | ||
@@ -116,0 +116,0 @@ }); |
/** | ||
* Requires space after opening object curly brace and before closing. | ||
* | ||
* Type: `Object` or `String` | ||
* Types: `Object` or `String` | ||
* | ||
@@ -60,3 +60,3 @@ * Values: `"all"` for strict mode, `"allButNested"` (*deprecated* use `"allExcept": ['}']`) | ||
var error = 'requireSpacesInsideObjectBrackets rule' + | ||
var error = this.getOptionName() + ' rule' + | ||
' requires string value \'all\' or \'allButNested\' or object'; | ||
@@ -105,6 +105,6 @@ | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: openingBracket, | ||
nextToken: nextToken, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required after opening curly brace' | ||
@@ -120,6 +120,6 @@ }); | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: prevToken, | ||
nextToken: closingBracket, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required before closing curly brace' | ||
@@ -126,0 +126,0 @@ }); |
/** | ||
* Requires space after opening round bracket and before closing. | ||
* | ||
* Type: `Object` or `String` | ||
* Types: `Object` or `String` | ||
* | ||
@@ -57,3 +57,3 @@ * Values: `"all"` for strict mode, `"allButNested"` | ||
var error = 'requireSpacesInsideParentheses rule' + | ||
var error = this.getOptionName() + ' rule' + | ||
' requires string value \'all\' or \'allButNested\' or object'; | ||
@@ -98,10 +98,4 @@ | ||
function isComment(position) { | ||
return file.getComments().some(function(comment) { | ||
return position >= comment.range[0] && position < comment.range[1]; | ||
}); | ||
} | ||
file.iterateTokenByValue('(', function(token) { | ||
var nextToken = file.getNextToken(token); | ||
var nextToken = file.getNextToken(token, {includeComments: true}); | ||
var value = nextToken.value; | ||
@@ -118,13 +112,11 @@ | ||
if ( | ||
(token.range[1] === nextToken.range[0] && | ||
token.loc.end.line === nextToken.loc.start.line) || | ||
isComment(token.range[1]) | ||
) { | ||
errors.add('Missing space after opening round bracket', token.loc.end); | ||
} | ||
errors.assert.whitespaceBetween({ | ||
token: token, | ||
nextToken: nextToken, | ||
message: 'Missing space after opening round bracket' | ||
}); | ||
}); | ||
file.iterateTokenByValue(')', function(token) { | ||
var prevToken = file.getPrevToken(token); | ||
var prevToken = file.getPrevToken(token, {includeComments: true}); | ||
var value = prevToken.value; | ||
@@ -148,13 +140,9 @@ | ||
if ( | ||
(token.range[0] === prevToken.range[1] && | ||
token.loc.end.line === prevToken.loc.start.line) || | ||
isComment(token.range[0] - 1) | ||
) { | ||
errors.add('Missing space before closing round bracket', | ||
token.loc.end.line, | ||
token.loc.end.column - 2); | ||
} | ||
errors.assert.whitespaceBetween({ | ||
token: prevToken, | ||
nextToken: token, | ||
message: 'Missing space before closing round bracket' | ||
}); | ||
}); | ||
} | ||
}; |
/** | ||
* Requires an extra comma following the final element of an array or object literal. | ||
* | ||
* Type: `Boolean` or `Object` | ||
* Types: `Boolean` or `Object` | ||
* | ||
@@ -53,16 +53,16 @@ * Values: | ||
module.exports.prototype = { | ||
configure: function(requireTrailingComma) { | ||
configure: function(options) { | ||
if (typeof requireTrailingComma === 'object') { | ||
if ('ignoreSingleValue' in requireTrailingComma) { | ||
if (typeof options === 'object') { | ||
if ('ignoreSingleValue' in options) { | ||
assert( | ||
requireTrailingComma.ignoreSingleValue === true, | ||
'requireTrailingComma option ignoreSingleValue requires true value or should be removed' | ||
options.ignoreSingleValue === true, | ||
this.getOptionName() + ' option ignoreSingleValue requires true value or should be removed' | ||
); | ||
this._ignoreSingleValue = true; | ||
} | ||
if ('ignoreSingleLine' in requireTrailingComma) { | ||
if ('ignoreSingleLine' in options) { | ||
assert( | ||
requireTrailingComma.ignoreSingleLine === true, | ||
'requireTrailingComma option ignoreSingleLine requires true value or should be removed' | ||
options.ignoreSingleLine === true, | ||
this.getOptionName() + ' option ignoreSingleLine requires true value or should be removed' | ||
); | ||
@@ -73,4 +73,4 @@ this._ignoreSingleLine = true; | ||
assert( | ||
requireTrailingComma === true, | ||
'requireTrailingComma option requires true value or should be removed' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
@@ -77,0 +77,0 @@ } |
@@ -6,3 +6,3 @@ /** | ||
* | ||
* Values: `true` | ||
* Value: `true` | ||
* | ||
@@ -37,11 +37,7 @@ * #### Example | ||
configure: function(requireYodaConditions) { | ||
configure: function(options) { | ||
assert( | ||
typeof requireYodaConditions === 'boolean', | ||
'requireYodaConditions option requires boolean value' | ||
options === true, | ||
this.getOptionName() + ' option requires a true value or should be removed' | ||
); | ||
assert( | ||
requireYodaConditions === true, | ||
'requireYodaConditions option requires true value or should be removed' | ||
); | ||
this._operatorIndex = { | ||
@@ -48,0 +44,0 @@ '==': true, |
/** | ||
* Option to check `var that = this` expressions | ||
* | ||
* Type: `Array` or `String` | ||
* Types: `Array` or `String` | ||
* | ||
@@ -36,3 +36,3 @@ * Values: String value used for context local declaration | ||
Array.isArray(keywords) || typeof keywords === 'string', | ||
'safeContextKeyword option requires string or array value' | ||
this.getOptionName() + ' option requires string or array value' | ||
); | ||
@@ -39,0 +39,0 @@ |
/** | ||
* Validates indentation for switch statements and block statements | ||
* | ||
* Type: `Integer` or `String` or `Object` | ||
* Types: `Integer`, `String` or `Object` | ||
* | ||
@@ -117,20 +117,20 @@ * Values: | ||
configure: function(validateIndentation) { | ||
configure: function(options) { | ||
this._includeEmptyLines = false; | ||
if (typeof validateIndentation === 'object') { | ||
this._includeEmptyLines = (validateIndentation.includeEmptyLines === true); | ||
validateIndentation = validateIndentation.value; | ||
if (typeof options === 'object') { | ||
this._includeEmptyLines = (options.includeEmptyLines === true); | ||
options = options.value; | ||
} | ||
assert( | ||
validateIndentation === '\t' || | ||
(typeof validateIndentation === 'number' && validateIndentation > 0), | ||
'validateIndentation option requires a positive number of spaces or "\\t"' + | ||
options === '\t' || | ||
(typeof options === 'number' && options > 0), | ||
this.getOptionName() + ' option requires a positive number of spaces or "\\t"' + | ||
' or options object with "value" property' | ||
); | ||
if (typeof validateIndentation === 'number') { | ||
if (typeof options === 'number') { | ||
this._indentChar = ' '; | ||
this._indentSize = validateIndentation; | ||
this._indentSize = options; | ||
} else { | ||
@@ -197,6 +197,5 @@ this._indentChar = '\t'; | ||
function markAlternateBlockStatement(node, property) { | ||
var child = node[property]; | ||
if (child && child.type === 'BlockStatement') { | ||
markCheck(child); | ||
function markKeyword(node) { | ||
if (node) { | ||
markCheck(file.getPrevToken(file.getFirstNodeToken(node))); | ||
} | ||
@@ -251,7 +250,7 @@ } | ||
function getIndentationFromLine(i) { | ||
function getIndentationFromLine(line) { | ||
var rNotIndentChar = new RegExp('[^' + indentChar + ']'); | ||
var firstContent = lines[i].search(rNotIndentChar); | ||
var firstContent = line.search(rNotIndentChar); | ||
if (firstContent === -1) { | ||
firstContent = lines[i].length; | ||
firstContent = line.length; | ||
} | ||
@@ -262,17 +261,40 @@ return firstContent; | ||
function checkIndentations() { | ||
var lineAugment = 0; | ||
linesToCheck.forEach(function(line, i) { | ||
var actualIndentation = getIndentationFromLine(i); | ||
var lineNumber = i + 1; | ||
var actualIndentation = line.indentation; | ||
var expectedIndentation = getExpectedIndentation(line, actualIndentation); | ||
// do not augment this line considering this line changes indentation | ||
if (line.pop.length || line.push.length) { | ||
lineAugment = 0; | ||
} | ||
if (line.check) { | ||
if (actualIndentation !== expectedIndentation) { | ||
errors.add( | ||
'Expected indentation of ' + expectedIndentation + ' characters', | ||
i + 1, | ||
expectedIndentation | ||
); | ||
// correct the indentation so that future lines | ||
// can be validated appropriately | ||
actualIndentation = expectedIndentation; | ||
} | ||
errors.assert.indentation({ | ||
lineNumber: lineNumber, | ||
actual: actualIndentation, | ||
expected: expectedIndentation, | ||
indentChar: indentChar | ||
}); | ||
// for multiline statements, we need move subsequent lines over the correct | ||
// number of spaces to match the change made to the first line of the statement. | ||
lineAugment = expectedIndentation - actualIndentation; | ||
// correct the indentation so that future lines can be validated appropriately | ||
actualIndentation = expectedIndentation; | ||
} else if (!line.empty) { | ||
// in the case that we moved a previous line over a certain number spaces, | ||
// we need to move this line over as well, but technically, it's not an error | ||
errors.assert.indentation({ | ||
lineNumber: lineNumber, | ||
actual: actualIndentation, | ||
// Avoid going negative in the case that a previous line was overindented, | ||
// and now outdenting a line that is already at column zero. | ||
expected: Math.max(actualIndentation + lineAugment, 0), | ||
indentChar: indentChar, | ||
silent: true | ||
}); | ||
} | ||
@@ -327,3 +349,3 @@ | ||
line.pushAltLine.forEach(function(altLine) { | ||
expected.push(getIndentationFromLine(altLine) + (indentSize * indents)); | ||
expected.push(linesToCheck[altLine].indentation + (indentSize * indents)); | ||
}); | ||
@@ -401,9 +423,37 @@ } | ||
file.iterateNodesByType('ObjectExpression', function(node) { | ||
if (!isMultiline(node)) { | ||
return; | ||
} | ||
var children = getChildren(node); | ||
// only check objects that have children and that look like they are trying to adhere | ||
// to an indentation strategy, i.e. objects that have curly braces on their own lines. | ||
if (!children.length || node.loc.start.line === children[0].loc.start.line || | ||
node.loc.end.line === children[children.length - 1].loc.end.line) { | ||
return; | ||
} | ||
markChildren(node); | ||
markPop(node, 1); | ||
markPush(node, 1); | ||
markEndCheck(node); | ||
markPushAlt(node); | ||
}); | ||
file.iterateNodesByType('IfStatement', function(node) { | ||
markAlternateBlockStatement(node, 'alternate'); | ||
markKeyword(node.alternate); | ||
}); | ||
file.iterateNodesByType('TryStatement', function(node) { | ||
markAlternateBlockStatement(node, 'handler'); | ||
markAlternateBlockStatement(node, 'finalizer'); | ||
if (!isMultiline(node)) { | ||
return; | ||
} | ||
var handler = node.handlers && node.handlers.length ? node.handlers[0] : node.handler; | ||
if (handler) { | ||
markCheck(handler); | ||
} | ||
markKeyword(node.finalizer); | ||
}); | ||
@@ -455,8 +505,39 @@ | ||
if (_this._includeEmptyLines) { | ||
file.getLines().forEach(function(line, i) { | ||
if (line.match(/^\s*$/)) { | ||
linesToCheck[i].check = true; | ||
linesToCheck.forEach(function(line) { | ||
if (line.empty) { | ||
line.check = true; | ||
} | ||
}); | ||
} | ||
// starting from the bottom, which allows back to back comments to be checked, mark comments | ||
file.getComments().concat().reverse().forEach(function(node) { | ||
var startLine = node.loc.start.line; | ||
var firstToken = file.getFirstTokenOnLine(startLine, {includeComments: true}); | ||
var nextToken = file.getNextToken(firstToken, {includeComments: true}); | ||
var nextStartLine = nextToken.loc.start.line; | ||
var nextLine = linesToCheck[nextStartLine - 1]; | ||
// ignore if not the only token on the line, or not right above another checked line | ||
if (firstToken !== node || startLine === nextStartLine || !nextLine.check) { | ||
return; | ||
} | ||
// ignore if next line is a case statement, which is kind of hacky, but avoids | ||
// additional complexity for what qualifies as an outdent | ||
if (nextToken && nextToken.type === 'Keyword' && | ||
(nextToken.value === 'case' || nextToken.value === 'default')) { | ||
return; | ||
} | ||
// ignore if above a line that both introduces and ends an ident, | ||
// which catches cases like a comment above an `else if`, but not nested ifs. | ||
if (nextLine.push.length && nextLine.pop.length) { | ||
return; | ||
} | ||
markCheck(node); | ||
}); | ||
} | ||
@@ -471,5 +552,4 @@ | ||
var lines = file.getLinesWithCommentsRemoved(errors); | ||
var indentStack = [0]; | ||
var linesToCheck = lines.map(function() { | ||
var linesToCheck = file.getLines().map(function(line) { | ||
return { | ||
@@ -479,3 +559,5 @@ push: [], | ||
pop: [], | ||
check: false | ||
check: false, | ||
indentation: getIndentationFromLine(line), | ||
empty: line.match(/^\s*$/) | ||
}; | ||
@@ -482,0 +564,0 @@ }); |
@@ -8,3 +8,3 @@ var assert = require('assert'); | ||
configure: function(options) { | ||
assert(typeof options === 'object', 'validateJSDoc option requires object value'); | ||
assert(typeof options === 'object', this.getOptionName() + ' option requires object value'); | ||
this._options = options; | ||
@@ -11,0 +11,0 @@ }, |
@@ -36,3 +36,3 @@ /** | ||
typeof options === 'string' || typeof options === 'object', | ||
'validateLineBreaks option requires string or object value' | ||
this.getOptionName() + ' option requires string or object value' | ||
); | ||
@@ -64,13 +64,10 @@ | ||
var lineBreaks = file.getSource().match(/\r\n|\r|\n/g); | ||
for (var i = 0, len = lineBreaks.length; i < len; i++) { | ||
if (lineBreaks[i] !== this._allowedLineBreak) { | ||
file.getLineBreaks().some(function(lineBreak, i) { | ||
if (lineBreak !== this._allowedLineBreak) { | ||
errors.add('Invalid line break', i + 1, lines[i].length); | ||
if (this._reportOncePerFile) { | ||
break; | ||
} | ||
return this._reportOncePerFile; | ||
} | ||
} | ||
}, this); | ||
} | ||
}; |
@@ -38,9 +38,9 @@ /** | ||
configure: function(validateParameterSeparator) { | ||
configure: function(options) { | ||
assert( | ||
typeof validateParameterSeparator === 'string' && /^[ ]?,[ ]?$/.test(validateParameterSeparator), | ||
'validateParameterSpacing option requires string value containing only a comma and optional spaces' | ||
typeof options === 'string' && /^[ ]?,[ ]?$/.test(options), | ||
this.getOptionName() + ' option requires string value containing only a comma and optional spaces' | ||
); | ||
this._separator = validateParameterSeparator; | ||
this._separator = options; | ||
}, | ||
@@ -68,6 +68,6 @@ | ||
if (whitespaceBeforeComma) { | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: prevParamToken, | ||
nextToken: punctuatorToken, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required after function parameter \'' + prevParamToken.value + '\'' | ||
@@ -86,6 +86,6 @@ }); | ||
if (whitespaceAfterComma) { | ||
errors.assert.whitespaceBetween({ | ||
errors.assert.spacesBetween({ | ||
token: punctuatorToken, | ||
nextToken: nextParamToken, | ||
spaces: 1, | ||
exactly: 1, | ||
message: 'One space required before function parameter \'' + nextParamToken.value + '\'' | ||
@@ -92,0 +92,0 @@ }); |
/** | ||
* Requires all quote marks to be either the supplied value, or consistent if `true` | ||
* | ||
* Type: `String` or `Object` | ||
* Types: `Boolean`, `String` or `Object` | ||
* | ||
@@ -69,3 +69,3 @@ * Values: | ||
typeof quoteMark.escape === 'boolean' && quoteMark.mark !== undefined, | ||
'validateQuoteMarks option requires the "escape" and "mark" property to be defined' | ||
this.getOptionName() + ' option requires the "escape" and "mark" property to be defined' | ||
); | ||
@@ -78,3 +78,3 @@ this._allowEscape = quoteMark.escape; | ||
quoteMark === '"' || quoteMark === '\'' || quoteMark === true, | ||
'validateQuoteMarks option requires \'"\', "\'", or boolean true' | ||
this.getOptionName() + ' option requires \'"\', "\'", or boolean true' | ||
); | ||
@@ -81,0 +81,0 @@ |
@@ -7,2 +7,4 @@ var defaultEsprima = require('esprima'); | ||
var MAX_FIX_ATTEMPTS = 5; | ||
/** | ||
@@ -80,42 +82,112 @@ * Starts Code Style checking process. | ||
/** | ||
* Parses source code into an AST | ||
* Checks file provided with a string. | ||
* @param {String} source | ||
* @param {String} [filename='input'] | ||
* @returns {Errors} | ||
*/ | ||
checkString: function(source, filename) { | ||
filename = filename || 'input'; | ||
var sourceTree; | ||
var parseError; | ||
try { | ||
sourceTree = JsFile.parse(source, this._esprima, this._configuration.getEsprimaOptions()); | ||
} catch (e) { | ||
parseError = e; | ||
} | ||
var file = this._createJsFileInstance(filename, source, sourceTree); | ||
var errors = new Errors(file, this._verbose); | ||
if (this._maxErrorsExceeded) { | ||
return errors; | ||
} | ||
if (parseError) { | ||
this._addParseError(errors, parseError); | ||
return errors; | ||
} | ||
this._checkJsFile(file, errors); | ||
return errors; | ||
}, | ||
/** | ||
* Checks a file specified using JsFile instance. | ||
* Fills Errors instance with validation errors. | ||
* | ||
* @param {String} str the source code to parse | ||
* @return {Object} the output AST | ||
* @throws Error on invalid source code. | ||
* @param {JsFile} file | ||
* @param {Errors} errors | ||
* @private | ||
*/ | ||
_parse: function(str) { | ||
var hashbang = str.indexOf('#!') === 0; | ||
var tree; | ||
_checkJsFile: function(file, errors) { | ||
if (this._maxErrorsExceeded) { | ||
return; | ||
} | ||
// Convert bin annotation to a comment | ||
if (hashbang) { | ||
str = '//' + str.substr(2); | ||
var errorFilter = this._configuration.getErrorFilter(); | ||
this._configuredRules.forEach(function(rule) { | ||
errors.setCurrentRule(rule.getOptionName()); | ||
rule.check(file, errors); | ||
}, this); | ||
this._configuration.getUnsupportedRuleNames().forEach(function(rulename) { | ||
errors.add('Unsupported rule: ' + rulename, 1, 0); | ||
}); | ||
// sort errors list to show errors as they appear in source | ||
errors.getErrorList().sort(function(a, b) { | ||
return (a.line - b.line) || (a.column - b.column); | ||
}); | ||
if (errorFilter) { | ||
errors.filter(errorFilter); | ||
} | ||
// Strip special case code like iOS instrumentation imports: `#import 'abc.js';` | ||
str = str.replace(/^#!?[^\n]+\n/gm, ''); | ||
if (this._maxErrorsEnabled()) { | ||
this._maxErrorsExceeded = this._errorsFound + errors.getErrorCount() > this._maxErrors; | ||
errors.stripErrorList(Math.max(0, this._maxErrors - this._errorsFound)); | ||
} | ||
var esprimaOptions = { | ||
tolerant: true | ||
}; | ||
var esprimaOptionsFromConfig = this._configuration.getEsprimaOptions(); | ||
for (var key in esprimaOptionsFromConfig) { | ||
esprimaOptions[key] = esprimaOptionsFromConfig[key]; | ||
this._errorsFound += errors.getErrorCount(); | ||
}, | ||
/** | ||
* Adds parse error to the error list. | ||
* | ||
* @param {Errors} errors | ||
* @param {Error} parseError | ||
* @private | ||
*/ | ||
_addParseError: function(errors, parseError) { | ||
if (this._maxErrorsExceeded) { | ||
return; | ||
} | ||
// Set required options | ||
esprimaOptions.loc = true; | ||
esprimaOptions.range = true; | ||
esprimaOptions.comment = true; | ||
esprimaOptions.tokens = true; | ||
esprimaOptions.sourceType = 'module'; | ||
tree = this._esprima.parse(str, esprimaOptions); | ||
errors.setCurrentRule('parseError'); | ||
errors.add(parseError.description, parseError.lineNumber, parseError.column); | ||
// Remove the bin annotation comment | ||
if (hashbang) { | ||
tree.comments = tree.comments.slice(1); | ||
if (this._maxErrorsEnabled()) { | ||
this._errorsFound += 1; | ||
this._maxErrorsExceeded = this._errorsFound >= this._maxErrors; | ||
} | ||
}, | ||
return tree; | ||
/** | ||
* Creates configured JsFile instance. | ||
* | ||
* @param {String} filename | ||
* @param {String} source | ||
* @param {Object} sourceTree | ||
* @private | ||
*/ | ||
_createJsFileInstance: function(filename, source, sourceTree) { | ||
return new JsFile(filename, source, sourceTree, { | ||
es3: this._configuration.isES3Enabled(), | ||
es6: this._configuration.isESNextEnabled() | ||
}); | ||
}, | ||
@@ -125,14 +197,18 @@ | ||
* Checks file provided with a string. | ||
* @param {String} str | ||
* @param {String} filename | ||
* @returns {Errors} | ||
* @param {String} source | ||
* @param {String} [filename='input'] | ||
* @returns {{output: String, errors: Errors}} | ||
*/ | ||
checkString: function(str, filename) { | ||
fixString: function(source, filename) { | ||
if (this._maxErrorsEnabled()) { | ||
throw new Error('Cannot autofix when `maxError` option is enabled'); | ||
} | ||
filename = filename || 'input'; | ||
var tree; | ||
var sourceTree; | ||
var parseError; | ||
try { | ||
tree = this._parse(str); | ||
sourceTree = JsFile.parse(source, this._esprima, this._configuration.getEsprimaOptions()); | ||
} catch (e) { | ||
@@ -142,47 +218,43 @@ parseError = e; | ||
var file = new JsFile(filename, str, tree, { | ||
es3: this._configuration.isES3Enabled(), | ||
es6: this._configuration.isESNextEnabled() | ||
}); | ||
if (parseError) { | ||
var parseErrors = new Errors(this._createJsFileInstance(filename, source, sourceTree), this._verbose); | ||
this._addParseError(parseErrors, parseError); | ||
return {output: source, errors: parseErrors}; | ||
} else { | ||
var attempt = 0; | ||
var errors; | ||
var file; | ||
var errors = new Errors(file, this._verbose); | ||
var errorFilter = this._configuration.getErrorFilter(); | ||
do { | ||
file = this._createJsFileInstance(filename, source, sourceTree); | ||
errors = new Errors(file, this._verbose); | ||
if (!this._maxErrorsExceeded) { | ||
if (parseError) { | ||
errors.setCurrentRule('parseError'); | ||
errors.add(parseError.description, parseError.lineNumber, parseError.column); | ||
// Changes to current sources are made in rules through assertions. | ||
this._checkJsFile(file, errors); | ||
return errors; | ||
} | ||
var hasFixes = errors.getErrorList().some(function(err) { | ||
return err.fixed; | ||
}); | ||
this._configuredRules.forEach(function(rule) { | ||
errors.setCurrentRule(rule.getOptionName()); | ||
rule.check(file, errors); | ||
}, this); | ||
if (!hasFixes) { | ||
break; | ||
} | ||
this._configuration.getUnsupportedRuleNames().forEach(function(rulename) { | ||
errors.add('Unsupported rule: ' + rulename, 1, 0); | ||
}); | ||
source = file.render(); | ||
sourceTree = JsFile.parse(source, this._esprima, this._configuration.getEsprimaOptions()); | ||
// sort errors list to show errors as they appear in source | ||
errors.getErrorList().sort(function(a, b) { | ||
return (a.line - b.line) || (a.column - b.column); | ||
}); | ||
attempt++; | ||
} while (attempt < MAX_FIX_ATTEMPTS); | ||
if (errorFilter) { | ||
errors.filter(errorFilter); | ||
} | ||
if (this._maxErrors !== null && !isNaN(this._maxErrors)) { | ||
if (!this._maxErrorsExceeded) { | ||
this._maxErrorsExceeded = this._errorsFound + errors.getErrorCount() > this._maxErrors; | ||
} | ||
errors.stripErrorList(Math.max(0, this._maxErrors - this._errorsFound)); | ||
} | ||
this._errorsFound += errors.getErrorCount(); | ||
return {output: source, errors: errors}; | ||
} | ||
}, | ||
return errors; | ||
/** | ||
* Returns `true` if max erros limit is enabled. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
_maxErrorsEnabled: function() { | ||
return this._maxErrors !== null && !isNaN(this._maxErrors); | ||
}, | ||
@@ -189,0 +261,0 @@ |
@@ -27,25 +27,4 @@ var utils = require('util'); | ||
TokenAssert.prototype.whitespaceBetween = function(options) { | ||
var token = options.token; | ||
var nextToken = options.nextToken; | ||
if (options.hasOwnProperty('spaces')) { | ||
var spaces = options.spaces; | ||
if (nextToken.loc.start.line === token.loc.end.line && | ||
(nextToken.loc.start.column - token.loc.end.column) !== spaces | ||
) { | ||
this.emit('error', { | ||
message: options.message || | ||
spaces + ' spaces required between ' + token.value + ' and ' + nextToken.value, | ||
line: token.loc.end.line, | ||
column: token.loc.end.column | ||
}); | ||
} | ||
} else { | ||
if (nextToken.range[0] === token.range[1]) { | ||
this.emit('error', { | ||
message: options.message || 'Missing space between ' + token.value + ' and ' + nextToken.value, | ||
line: token.loc.end.line, | ||
column: token.loc.end.column | ||
}); | ||
} | ||
} | ||
options.atLeast = 1; | ||
this.spacesBetween(options); | ||
}; | ||
@@ -62,17 +41,8 @@ | ||
TokenAssert.prototype.noWhitespaceBetween = function(options) { | ||
var token = options.token; | ||
var nextToken = options.nextToken; | ||
if (nextToken.range[0] !== token.range[1] && | ||
(options.disallowNewLine || token.loc.end.line === nextToken.loc.start.line) | ||
) { | ||
this.emit('error', { | ||
message: options.message || 'Unexpected whitespace between ' + token.value + ' and ' + nextToken.value, | ||
line: token.loc.end.line, | ||
column: token.loc.end.column | ||
}); | ||
} | ||
options.exactly = 0; | ||
this.spacesBetween(options); | ||
}; | ||
/** | ||
* Requires tokens to be on the same line. | ||
* Requires to have the whitespace between specified tokens with the provided options. | ||
* | ||
@@ -82,6 +52,13 @@ * @param {Object} options.token | ||
* @param {String} [options.message] | ||
* @param {Object} [options.atLeast] At least how many spaces the tokens are apart | ||
* @param {Object} [options.atMost] At most how many spaces the tokens are apart | ||
* @param {Object} [options.exactly] Exactly how many spaces the tokens are apart | ||
* @param {Boolean} [options.disallowNewLine=false] | ||
*/ | ||
TokenAssert.prototype.sameLine = function(options) { | ||
TokenAssert.prototype.spacesBetween = function(options) { | ||
var token = options.token; | ||
var nextToken = options.nextToken; | ||
var atLeast = options.atLeast; | ||
var atMost = options.atMost; | ||
var exactly = options.exactly; | ||
@@ -92,8 +69,49 @@ if (!token || !nextToken) { | ||
if (token.loc.end.line !== nextToken.loc.start.line) { | ||
this._validateOptions(options); | ||
if (!options.disallowNewLine && token.loc.end.line !== nextToken.loc.start.line) { | ||
return; | ||
} | ||
// Only attempt to remove or add lines if there are no comments between the two nodes | ||
// as this prevents accidentally moving a valid token onto a line comment ed line | ||
var fixed = this._file.getNextToken(options.token, {includeComments: true}) === nextToken; | ||
var emitError = function(countPrefix, spaceCount) { | ||
if (fixed) { | ||
nextToken.whitespaceBefore = new Array(spaceCount + 1).join(' '); | ||
} | ||
var msgPostfix = token.value + ' and ' + nextToken.value; | ||
if (!options.message) { | ||
if (exactly === 0) { | ||
// support noWhitespaceBetween | ||
options.message = 'Unexpected whitespace between ' + msgPostfix; | ||
} else if (exactly !== undefined) { | ||
// support whitespaceBetween (spaces option) | ||
options.message = spaceCount + ' spaces required between ' + msgPostfix; | ||
} else if (atLeast === 1 && atMost === undefined) { | ||
// support whitespaceBetween (no spaces option) | ||
options.message = 'Missing space between ' + msgPostfix; | ||
} else { | ||
options.message = countPrefix + ' ' + spaceCount + ' spaces required between ' + msgPostfix; | ||
} | ||
} | ||
this.emit('error', { | ||
message: options.message || token.value + ' and ' + nextToken.value + ' should be on the same line', | ||
message: options.message, | ||
line: token.loc.end.line, | ||
column: token.loc.end.column | ||
column: token.loc.end.column, | ||
fixed: fixed | ||
}); | ||
}.bind(this); | ||
var spacesBetween = Math.abs(nextToken.range[0] - token.range[1]); | ||
if (atLeast !== undefined && spacesBetween < atLeast) { | ||
emitError('at least', atLeast); | ||
} else if (atMost !== undefined && spacesBetween > atMost) { | ||
emitError('at most', atMost); | ||
} else if (exactly !== undefined && spacesBetween !== exactly) { | ||
emitError('exactly', exactly); | ||
} | ||
@@ -103,2 +121,127 @@ }; | ||
/** | ||
* Requires the specified line to have the expected indentation. | ||
* | ||
* @param {Number} options.lineNumber | ||
* @param {Number} options.actual | ||
* @param {Number} options.expected | ||
* @param {String} options.indentChar | ||
* @param {Boolean} [options.silent] if true, will suppress error emission but still fix whitespace | ||
*/ | ||
TokenAssert.prototype.indentation = function(options) { | ||
var lineNumber = options.lineNumber; | ||
var actual = options.actual; | ||
var expected = options.expected; | ||
var indentChar = options.indentChar; | ||
if (actual === expected) { | ||
return; | ||
} | ||
if (!options.silent) { | ||
this.emit('error', { | ||
message: 'Expected indentation of ' + expected + ' characters', | ||
line: lineNumber, | ||
column: expected, | ||
fixed: true | ||
}); | ||
} | ||
var token = this._file.getFirstTokenOnLine(lineNumber, {includeComments: true}); | ||
var newWhitespace = (new Array(expected + 1)).join(indentChar); | ||
if (!token) { | ||
this._setEmptyLineIndentation(lineNumber, newWhitespace); | ||
return; | ||
} | ||
this._updateWhitespaceByLine(token, function(lines) { | ||
lines[lines.length - 1] = newWhitespace; | ||
return lines; | ||
}); | ||
if (token.isComment) { | ||
this._updateCommentWhitespace(token, indentChar, actual, expected); | ||
} | ||
}; | ||
/** | ||
* Updates the whitespace of a line by passing split lines to a callback function | ||
* for editing. | ||
* | ||
* @param {Number} lineNumber | ||
* @param {Function} callback | ||
*/ | ||
TokenAssert.prototype._updateWhitespaceByLine = function(token, callback) { | ||
var lineBreak = this._file.getLineBreakStyle(); | ||
var lines = token.whitespaceBefore.split(/\r\n|\r|\n/); | ||
lines = callback(lines); | ||
token.whitespaceBefore = lines.join(lineBreak); | ||
}; | ||
/** | ||
* Updates the whitespace of a line by passing split lines to a callback function | ||
* for editing. | ||
* | ||
* @param {Number} lineNumber | ||
* @param {Function} callback | ||
*/ | ||
TokenAssert.prototype._updateCommentWhitespace = function(token, indentChar, actual, expected) { | ||
var difference = expected - actual; | ||
var tokenLines = token.value.split(/\r\n|\r|\n/); | ||
var i = 1; | ||
if (difference >= 0) { | ||
var lineWhitespace = (new Array(difference + 1)).join(indentChar); | ||
for (; i < tokenLines.length; i++) { | ||
tokenLines[i] = tokenLines[i] === '' ? '' : lineWhitespace + tokenLines[i]; | ||
} | ||
} else { | ||
for (; i < tokenLines.length; i++) { | ||
tokenLines[i] = tokenLines[i].substring(-difference); | ||
} | ||
} | ||
token.value = tokenLines.join(this._file.getLineBreakStyle()); | ||
}; | ||
/** | ||
* Fixes the indentation of a line that has no tokens on it | ||
* | ||
* @param {Number} lineNumber | ||
* @param {String} newWhitespace | ||
*/ | ||
TokenAssert.prototype._setEmptyLineIndentation = function(lineNumber, newWhitespace) { | ||
var token; | ||
do { | ||
token = this._file.getFirstTokenOnLine(++lineNumber, {includeComments: true}); | ||
} while (!token); | ||
this._updateWhitespaceByLine(token, function(lines) { | ||
if (lines[0] !== '') { | ||
lines[0] = newWhitespace; | ||
} | ||
for (var i = 1; i < lines.length; i++) { | ||
lines[i] = newWhitespace; | ||
} | ||
return lines; | ||
}); | ||
}; | ||
/** | ||
* Requires tokens to be on the same line. | ||
* | ||
* @param {Object} options.token | ||
* @param {Object} options.nextToken | ||
* @param {String} [options.message] | ||
*/ | ||
TokenAssert.prototype.sameLine = function(options) { | ||
options.exactly = 0; | ||
this.linesBetween(options); | ||
}; | ||
/** | ||
* Requires tokens to be on different lines. | ||
@@ -111,4 +254,24 @@ * | ||
TokenAssert.prototype.differentLine = function(options) { | ||
options.atLeast = 1; | ||
this.linesBetween(options); | ||
}; | ||
/** | ||
* Requires tokens to have a certain amount of lines between them. | ||
* Set at least one of atLeast or atMost OR set exactly. | ||
* | ||
* @param {Object} options.token | ||
* @param {Object} options.nextToken | ||
* @param {Object} [options.message] | ||
* @param {Object} [options.atLeast] At least how many lines the tokens are apart | ||
* @param {Object} [options.atMost] At most how many lines the tokens are apart | ||
* @param {Object} [options.exactly] Exactly how many lines the tokens are apart | ||
*/ | ||
TokenAssert.prototype.linesBetween = function(options) { | ||
var token = options.token; | ||
var nextToken = options.nextToken; | ||
var atLeast = options.atLeast; | ||
var atMost = options.atMost; | ||
var exactly = options.exactly; | ||
@@ -119,8 +282,44 @@ if (!token || !nextToken) { | ||
if (token.loc.end.line === nextToken.loc.start.line) { | ||
this._validateOptions(options); | ||
// Only attempt to remove or add lines if there are no comments between the two nodes | ||
// as this prevents accidentally moving a valid token onto a line comment ed line | ||
var fixed = this._file.getNextToken(options.token, {includeComments: true}) === nextToken; | ||
var linesBetween = Math.abs(token.loc.end.line - nextToken.loc.start.line); | ||
var emitError = function(countPrefix, lineCount) { | ||
var msgPrefix = token.value + ' and ' + nextToken.value; | ||
if (!options.message) { | ||
if (exactly === 0) { | ||
// support sameLine | ||
options.message = msgPrefix + ' should be on the same line'; | ||
} else if (atLeast === 1 && atMost === undefined) { | ||
// support differentLine | ||
options.message = msgPrefix + ' should be on different lines'; | ||
} else { | ||
// support linesBetween | ||
options.message = msgPrefix + ' should have ' + countPrefix + ' ' + lineCount + ' line(s) between them'; | ||
} | ||
} | ||
if (fixed) { | ||
this._augmentLineCount(nextToken, lineCount); | ||
} | ||
this.emit('error', { | ||
message: options.message || token.value + ' and ' + nextToken.value + ' should be on different lines', | ||
message: options.message, | ||
line: token.loc.end.line, | ||
column: token.loc.end.column | ||
column: token.loc.end.column, | ||
fixed: fixed | ||
}); | ||
}.bind(this); | ||
if (atLeast !== undefined && linesBetween < atLeast) { | ||
emitError('at least', atLeast); | ||
} else if (atMost !== undefined && linesBetween > atMost) { | ||
emitError('at most', atMost); | ||
} else if (exactly !== undefined && linesBetween !== exactly) { | ||
emitError('exactly', exactly); | ||
} | ||
@@ -130,2 +329,70 @@ }; | ||
/** | ||
* Throws errors if atLeast, atMost, and exactly options don't mix together properly or | ||
* if the tokens provided are equivalent. | ||
* | ||
* @param {Object} options.token | ||
* @param {Object} options.nextToken | ||
* @param {Object} [options.atLeast] At least how many spaces the tokens are apart | ||
* @param {Object} [options.atMost] At most how many spaces the tokens are apart | ||
* @param {Object} [options.exactly] Exactly how many spaces the tokens are apart | ||
* @throws {Error} If the options are non-sensical | ||
*/ | ||
TokenAssert.prototype._validateOptions = function(options) { | ||
var token = options.token; | ||
var nextToken = options.nextToken; | ||
var atLeast = options.atLeast; | ||
var atMost = options.atMost; | ||
var exactly = options.exactly; | ||
if (token === nextToken) { | ||
throw new Error('You cannot specify the same token as both token and nextToken'); | ||
} | ||
if (atLeast === undefined && | ||
atMost === undefined && | ||
exactly === undefined) { | ||
throw new Error('You must specify at least one option'); | ||
} | ||
if (exactly !== undefined && (atLeast !== undefined || atMost !== undefined)) { | ||
throw new Error('You cannot specify atLeast or atMost with exactly'); | ||
} | ||
if (atLeast !== undefined && atMost !== undefined && atMost < atLeast) { | ||
throw new Error('atLeast and atMost are in conflict'); | ||
} | ||
}; | ||
/** | ||
* Augments token whitespace to contain the correct number of newlines while preserving indentation | ||
* | ||
* @param {Object} token | ||
* @param {Number} lineCount | ||
*/ | ||
TokenAssert.prototype._augmentLineCount = function(token, lineCount) { | ||
if (lineCount === 0) { | ||
token.whitespaceBefore = ' '; | ||
return; | ||
} | ||
this._updateWhitespaceByLine(token, function(lines) { | ||
var currentLineCount = lines.length; | ||
var lastLine = lines[lines.length - 1]; | ||
if (currentLineCount <= lineCount) { | ||
// add additional lines that maintain the same indentation as the former last line | ||
for (; currentLineCount <= lineCount; currentLineCount++) { | ||
lines[lines.length - 1] = ''; | ||
lines.push(lastLine); | ||
} | ||
} else { | ||
// remove lines and then ensure that the new last line maintains the previous indentation | ||
lines = lines.slice(0, lineCount + 1); | ||
lines[lines.length - 1] = lastLine; | ||
} | ||
return lines; | ||
}); | ||
}; | ||
/** | ||
* Requires specific token before given. | ||
@@ -132,0 +399,0 @@ * |
@@ -35,2 +35,13 @@ var estraverse = require('estraverse'); | ||
keys: { | ||
JSXIdentifier: [], | ||
JSXNamespacedName: ['namespace', 'name'], | ||
JSXMemberExpression: ['object', 'property'], | ||
JSXEmptyExpression: [], | ||
JSXExpressionContainer: ['expression'], | ||
JSXElement: ['openingElement', 'closingElement', 'children'], | ||
JSXClosingElement: ['name'], | ||
JSXOpeningElement: ['name', 'attributes'], | ||
JSXAttribute: ['name', 'value'], | ||
JSXSpreadAttribute: ['argument'], | ||
JSXText: null, | ||
XJSIdentifier: [], | ||
@@ -37,0 +48,0 @@ XJSNamespacedName: ['namespace', 'name'], |
@@ -5,3 +5,3 @@ { | ||
"name": "jscs", | ||
"version": "1.11.3", | ||
"version": "1.12.0", | ||
"main": "lib/checker", | ||
@@ -70,10 +70,10 @@ "homepage": "https://github.com/jscs-dev/node-jscs", | ||
"dependencies": { | ||
"chalk": "~1.0.0", | ||
"cli-table": "~0.3.1", | ||
"colors": "~1.0.3", | ||
"commander": "~2.6.0", | ||
"esprima": "~1.2.4", | ||
"esprima-harmony-jscs": "1.1.0-tolerate-import", | ||
"estraverse": "~1.9.1", | ||
"esprima": "^1.2.5", | ||
"esprima-harmony-jscs": "1.1.0-templates", | ||
"estraverse": "^1.9.3", | ||
"exit": "~0.1.2", | ||
"glob": "~4.3.5", | ||
"glob": "^5.0.1", | ||
"lodash.assign": "~3.0.0", | ||
@@ -83,18 +83,17 @@ "minimatch": "~2.0.1", | ||
"strip-json-comments": "~1.0.2", | ||
"supports-color": "~1.2.0", | ||
"vow": "~0.4.8", | ||
"vow-fs": "~0.3.4", | ||
"xmlbuilder": "~2.5.0" | ||
"xmlbuilder": "^2.6.1" | ||
}, | ||
"devDependencies": { | ||
"browserify": "~8.1.3", | ||
"browserify": "^9.0.3", | ||
"coveralls": "~2.11.2", | ||
"has-ansi": "~1.0.1", | ||
"jshint": "~2.6.0", | ||
"mocha": "~2.1.0", | ||
"mocha": "^2.2.0", | ||
"regenerate": "~1.2.1", | ||
"rewire": "~2.1.5", | ||
"sinon": "~1.12.2", | ||
"rewire": "^2.3.1", | ||
"sinon": "^1.13.0", | ||
"unicode-7.0.0": "~0.1.5", | ||
"unit-coverage": "~3.3.0", | ||
"unit-coverage": "^3.4.0", | ||
"xml2js": "~0.4.4" | ||
@@ -114,3 +113,3 @@ }, | ||
"-t", | ||
"test/**/*.js", | ||
"test/specs/**/*.js", | ||
"-S", | ||
@@ -121,3 +120,3 @@ "relative", | ||
"-O", | ||
"tests=test" | ||
"tests=test/specs" | ||
] | ||
@@ -124,0 +123,0 @@ }, |
@@ -60,2 +60,6 @@ { | ||
], | ||
"requirePaddingNewLinesBeforeLineComments": { | ||
"allExcept": "firstAfterCurly" | ||
}, | ||
"requirePaddingNewLinesAfterBlocks": true, | ||
"safeContextKeyword": "_this", | ||
@@ -62,0 +66,0 @@ "validateLineBreaks": "LF", |
@@ -65,3 +65,4 @@ { | ||
"disallowNewlineBeforeBlockStatements": true, | ||
"disallowMultipleLineStrings": true | ||
"disallowMultipleLineStrings": true, | ||
"requireSpaceBeforeObjectValues": true | ||
} |
@@ -13,3 +13,2 @@ { | ||
"requireParenthesesAroundIIFE": true, | ||
"requireMultipleVarDecl": "onevar", | ||
"requireCommaBeforeLineBreak": true, | ||
@@ -68,2 +67,3 @@ "requireCamelCaseOrUpperCaseIdentifiers": true, | ||
}, | ||
"requirePaddingNewLinesBeforeLineComments": true, | ||
"validateLineBreaks": "LF", | ||
@@ -70,0 +70,0 @@ |
@@ -98,3 +98,6 @@ { | ||
"disallowTrailingWhitespace": true, | ||
"validateLineBreaks": "LF" | ||
"validateIndentation": 4, | ||
"validateLineBreaks": "LF", | ||
"requireSemicolons": true | ||
} |
@@ -35,2 +35,3 @@ [![Build Status](https://travis-ci.org/jscs-dev/node-jscs.svg?branch=master)](https://travis-ci.org/jscs-dev/node-jscs) | ||
* [Famous](http://famo.us/) | ||
* [less.js](http://lesscss.org/) | ||
* [Goodvidio](http://goodvid.io/) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
High entropy strings
Supply chain riskContains high entropy strings. This could be a sign of encrypted data, leaked secrets or obfuscated code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1291039
15
151
37871
37
25
1
+ Addedchalk@~1.0.0
+ Addedansi-regex@1.1.1(transitive)
+ Addedansi-styles@2.2.1(transitive)
+ Addedchalk@1.0.0(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedesprima-harmony-jscs@1.1.0-templates(transitive)
+ Addedget-stdin@4.0.1(transitive)
+ Addedglob@5.0.15(transitive)
+ Addedhas-ansi@1.0.3(transitive)
+ Addedlodash@3.10.1(transitive)
+ Addedstrip-ansi@2.0.1(transitive)
+ Addedsupports-color@1.3.1(transitive)
+ Addedxmlbuilder@2.6.5(transitive)
- Removedcolors@~1.0.3
- Removedsupports-color@~1.2.0
- Removedesprima-harmony-jscs@1.1.0-tolerate-import(transitive)
- Removedglob@4.3.5(transitive)
- Removedlodash@3.2.0(transitive)
- Removedsupports-color@1.2.1(transitive)
- Removedxmlbuilder@2.5.2(transitive)
Updatedesprima@^1.2.5
Updatedestraverse@^1.9.3
Updatedglob@^5.0.1
Updatedxmlbuilder@^2.6.1