mirror of https://github.com/lukechilds/node.git
Browse Source
Update ESLint to 3.0.0. This includes an enhancement to `no-unused-vars` such that it finds a few instances in our code base that it did not find previously (fixed in previous commits readying this for landing). PR-URL: https://github.com/nodejs/node/pull/7601 Reviewed-By: Michaël Zasso <mic.besace@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io>v4.x
Rich Trott
9 years ago
committed by
Myles Borins
546 changed files with 9689 additions and 10543 deletions
File diff suppressed because it is too large
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* @fileoverview Config to enable all rules. |
||||
|
* @author Robert Fletcher |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var fs = require("fs"), |
||||
|
path = require("path"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var ruleFiles = fs.readdirSync(path.resolve(__dirname, "../lib/rules")); |
||||
|
var enabledRules = ruleFiles.reduce(function(result, filename) { |
||||
|
result[path.basename(filename, ".js")] = "error"; |
||||
|
return result; |
||||
|
}, {}); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { rules: enabledRules }; |
@ -0,0 +1,148 @@ |
|||||
|
/** |
||||
|
* @fileoverview enforce a maximum file length |
||||
|
* @author Alberto Rodríguez |
||||
|
*/ |
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var lodash = require("lodash"); |
||||
|
var astUtils = require("../ast-utils"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "enforce a maximum number of lines per file", |
||||
|
category: "Stylistic Issues", |
||||
|
recommended: false |
||||
|
}, |
||||
|
|
||||
|
schema: [ |
||||
|
{ |
||||
|
oneOf: [ |
||||
|
{ |
||||
|
type: "integer", |
||||
|
minimum: 0 |
||||
|
}, |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
max: { |
||||
|
type: "integer", |
||||
|
minimum: 0 |
||||
|
}, |
||||
|
skipComments: { |
||||
|
type: "boolean" |
||||
|
}, |
||||
|
skipBlankLines: { |
||||
|
type: "boolean" |
||||
|
} |
||||
|
}, |
||||
|
additionalProperties: false |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var option = context.options[0], |
||||
|
max = 300; |
||||
|
|
||||
|
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { |
||||
|
max = option.max; |
||||
|
} |
||||
|
|
||||
|
if (typeof option === "number") { |
||||
|
max = option; |
||||
|
} |
||||
|
|
||||
|
var skipComments = option && option.skipComments; |
||||
|
var skipBlankLines = option && option.skipBlankLines; |
||||
|
|
||||
|
var sourceCode = context.getSourceCode(); |
||||
|
|
||||
|
/** |
||||
|
* Returns whether or not a token is a comment node type |
||||
|
* @param {Token} token The token to check |
||||
|
* @returns {boolean} True if the token is a comment node |
||||
|
*/ |
||||
|
function isCommentNodeType(token) { |
||||
|
return token && (token.type === "Block" || token.type === "Line"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the line numbers of a comment that don't have any code on the same line |
||||
|
* @param {Node} comment The comment node to check |
||||
|
* @returns {int[]} The line numbers |
||||
|
*/ |
||||
|
function getLinesWithoutCode(comment) { |
||||
|
var start = comment.loc.start.line; |
||||
|
var end = comment.loc.end.line; |
||||
|
|
||||
|
var token; |
||||
|
|
||||
|
token = comment; |
||||
|
do { |
||||
|
token = sourceCode.getTokenOrCommentBefore(token); |
||||
|
} while (isCommentNodeType(token)); |
||||
|
|
||||
|
if (token && astUtils.isTokenOnSameLine(token, comment)) { |
||||
|
start += 1; |
||||
|
} |
||||
|
|
||||
|
token = comment; |
||||
|
do { |
||||
|
token = sourceCode.getTokenOrCommentAfter(token); |
||||
|
} while (isCommentNodeType(token)); |
||||
|
|
||||
|
if (token && astUtils.isTokenOnSameLine(comment, token)) { |
||||
|
end -= 1; |
||||
|
} |
||||
|
|
||||
|
if (start <= end) { |
||||
|
return lodash.range(start, end + 1); |
||||
|
} |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
"Program:exit": function() { |
||||
|
var lines = sourceCode.lines.map(function(text, i) { |
||||
|
return { lineNumber: i + 1, text: text }; |
||||
|
}); |
||||
|
|
||||
|
if (skipBlankLines) { |
||||
|
lines = lines.filter(function(l) { |
||||
|
return l.text.trim() !== ""; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (skipComments) { |
||||
|
var comments = sourceCode.getAllComments(); |
||||
|
|
||||
|
var commentLines = lodash.flatten(comments.map(function(comment) { |
||||
|
return getLinesWithoutCode(comment); |
||||
|
})); |
||||
|
|
||||
|
lines = lines.filter(function(l) { |
||||
|
return !lodash.includes(commentLines, l.lineNumber); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (lines.length > max) { |
||||
|
context.report({ |
||||
|
loc: { line: 1, column: 0 }, |
||||
|
message: "File must be at most " + max + " lines long" |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,212 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to disallow mixed binary operators. |
||||
|
* @author Toru Nagashima |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var astUtils = require("../ast-utils.js"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"]; |
||||
|
var BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"]; |
||||
|
var COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="]; |
||||
|
var LOGICAL_OPERATORS = ["&&", "||"]; |
||||
|
var RELATIONAL_OPERATORS = ["in", "instanceof"]; |
||||
|
var ALL_OPERATORS = [].concat( |
||||
|
ARITHMETIC_OPERATORS, |
||||
|
BITWISE_OPERATORS, |
||||
|
COMPARISON_OPERATORS, |
||||
|
LOGICAL_OPERATORS, |
||||
|
RELATIONAL_OPERATORS |
||||
|
); |
||||
|
var DEFAULT_GROUPS = [ |
||||
|
ARITHMETIC_OPERATORS, |
||||
|
BITWISE_OPERATORS, |
||||
|
COMPARISON_OPERATORS, |
||||
|
LOGICAL_OPERATORS, |
||||
|
RELATIONAL_OPERATORS |
||||
|
]; |
||||
|
var TARGET_NODE_TYPE = /^(?:Binary|Logical)Expression$/; |
||||
|
|
||||
|
/** |
||||
|
* Normalizes options. |
||||
|
* |
||||
|
* @param {object|undefined} options - A options object to normalize. |
||||
|
* @returns {object} Normalized option object. |
||||
|
*/ |
||||
|
function normalizeOptions(options) { |
||||
|
var hasGroups = (options && options.groups && options.groups.length > 0); |
||||
|
var groups = hasGroups ? options.groups : DEFAULT_GROUPS; |
||||
|
var allowSamePrecedence = (options && options.allowSamePrecedence) !== false; |
||||
|
|
||||
|
return { |
||||
|
groups: groups, |
||||
|
allowSamePrecedence: allowSamePrecedence |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether any group which includes both given operator exists or not. |
||||
|
* |
||||
|
* @param {Array.<string[]>} groups - A list of groups to check. |
||||
|
* @param {string} left - An operator. |
||||
|
* @param {string} right - Another operator. |
||||
|
* @returns {boolean} `true` if such group existed. |
||||
|
*/ |
||||
|
function includesBothInAGroup(groups, left, right) { |
||||
|
return groups.some(function(group) { |
||||
|
return group.indexOf(left) !== -1 && group.indexOf(right) !== -1; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "disallow mixed binary operators", |
||||
|
category: "Stylistic Issues", |
||||
|
recommended: false |
||||
|
}, |
||||
|
schema: [ |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
groups: { |
||||
|
type: "array", |
||||
|
items: { |
||||
|
type: "array", |
||||
|
items: {enum: ALL_OPERATORS}, |
||||
|
minItems: 2, |
||||
|
uniqueItems: true |
||||
|
}, |
||||
|
uniqueItems: true |
||||
|
}, |
||||
|
allowSamePrecedence: { |
||||
|
type: "boolean" |
||||
|
} |
||||
|
}, |
||||
|
additionalProperties: false |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var sourceCode = context.getSourceCode(); |
||||
|
var options = normalizeOptions(context.options[0]); |
||||
|
|
||||
|
/** |
||||
|
* Checks whether a given node should be ignored by options or not. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. This is a BinaryExpression |
||||
|
* node or a LogicalExpression node. This parent node is one of |
||||
|
* them, too. |
||||
|
* @returns {boolean} `true` if the node should be ignored. |
||||
|
*/ |
||||
|
function shouldIgnore(node) { |
||||
|
var a = node; |
||||
|
var b = node.parent; |
||||
|
|
||||
|
return ( |
||||
|
!includesBothInAGroup(options.groups, a.operator, b.operator) || |
||||
|
( |
||||
|
options.allowSamePrecedence && |
||||
|
astUtils.getPrecedence(a) === astUtils.getPrecedence(b) |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether the operator of a given node is mixed with parent |
||||
|
* node's operator or not. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. This is a BinaryExpression |
||||
|
* node or a LogicalExpression node. This parent node is one of |
||||
|
* them, too. |
||||
|
* @returns {boolean} `true` if the node was mixed. |
||||
|
*/ |
||||
|
function isMixedWithParent(node) { |
||||
|
return ( |
||||
|
node.operator !== node.parent.operator && |
||||
|
!astUtils.isParenthesised(sourceCode, node) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the operator token of a given node. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. This is a BinaryExpression |
||||
|
* node or a LogicalExpression node. |
||||
|
* @returns {Token} The operator token of the node. |
||||
|
*/ |
||||
|
function getOperatorToken(node) { |
||||
|
var token = sourceCode.getTokenAfter(node.left); |
||||
|
|
||||
|
while (token.value === ")") { |
||||
|
token = sourceCode.getTokenAfter(token); |
||||
|
} |
||||
|
|
||||
|
return token; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports both the operator of a given node and the operator of the |
||||
|
* parent node. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. This is a BinaryExpression |
||||
|
* node or a LogicalExpression node. This parent node is one of |
||||
|
* them, too. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportBothOperators(node) { |
||||
|
var parent = node.parent; |
||||
|
var left = (parent.left === node) ? node : parent; |
||||
|
var right = (parent.left !== node) ? node : parent; |
||||
|
var message = |
||||
|
"Unexpected mix of '" + left.operator + "' and '" + |
||||
|
right.operator + "'."; |
||||
|
|
||||
|
context.report({ |
||||
|
node: left, |
||||
|
loc: getOperatorToken(left).loc.start, |
||||
|
message: message |
||||
|
}); |
||||
|
context.report({ |
||||
|
node: right, |
||||
|
loc: getOperatorToken(right).loc.start, |
||||
|
message: message |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks between the operator of this node and the operator of the |
||||
|
* parent node. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function check(node) { |
||||
|
if (TARGET_NODE_TYPE.test(node.parent.type) && |
||||
|
isMixedWithParent(node) && |
||||
|
!shouldIgnore(node) |
||||
|
) { |
||||
|
reportBothOperators(node); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
BinaryExpression: check, |
||||
|
LogicalExpression: check |
||||
|
}; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,52 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to disallow use of Object.prototype builtins on objects |
||||
|
* @author Andrew Levine |
||||
|
*/ |
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "disallow calling some `Object.prototype` methods directly on objects", |
||||
|
category: "Possible Errors", |
||||
|
recommended: false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var DISALLOWED_PROPS = [ |
||||
|
"hasOwnProperty", |
||||
|
"isPrototypeOf", |
||||
|
"propertyIsEnumerable" |
||||
|
]; |
||||
|
|
||||
|
/** |
||||
|
* Reports if a disallowed property is used in a CallExpression |
||||
|
* @param {ASTNode} node The CallExpression node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function disallowBuiltIns(node) { |
||||
|
if (node.callee.type !== "MemberExpression" || node.callee.computed) { |
||||
|
return; |
||||
|
} |
||||
|
var propName = node.callee.property.name; |
||||
|
|
||||
|
if (DISALLOWED_PROPS.indexOf(propName) > -1) { |
||||
|
context.report({ |
||||
|
message: "Do not access Object.prototype method '{{prop}}' from target object.", |
||||
|
loc: node.callee.property.loc.start, |
||||
|
data: {prop: propName}, |
||||
|
node: node |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
CallExpression: disallowBuiltIns |
||||
|
}; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,150 @@ |
|||||
|
/** |
||||
|
* @fileoverview Disallow renaming import, export, and destructured assignments to the same name. |
||||
|
* @author Kai Cataldo |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "disallow renaming import, export, and destructured assignments to the same name", |
||||
|
category: "ECMAScript 6", |
||||
|
recommended: false |
||||
|
}, |
||||
|
fixable: "code", |
||||
|
schema: [ |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
ignoreDestructuring: { type: "boolean" }, |
||||
|
ignoreImport: { type: "boolean" }, |
||||
|
ignoreExport: { type: "boolean" } |
||||
|
}, |
||||
|
additionalProperties: false |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var options = context.options[0] || {}, |
||||
|
ignoreDestructuring = options.ignoreDestructuring === true, |
||||
|
ignoreImport = options.ignoreImport === true, |
||||
|
ignoreExport = options.ignoreExport === true; |
||||
|
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Reports error for unnecessarily renamed assignments |
||||
|
* @param {ASTNode} node - node to report |
||||
|
* @param {ASTNode} initial - node with initial name value |
||||
|
* @param {ASTNode} result - node with new name value |
||||
|
* @param {string} type - the type of the offending node |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportError(node, initial, result, type) { |
||||
|
var name = initial.type === "Identifier" ? initial.name : initial.value; |
||||
|
|
||||
|
return context.report({ |
||||
|
node: node, |
||||
|
message: "{{type}} {{name}} unnecessarily renamed.", |
||||
|
data: { |
||||
|
name: name, |
||||
|
type: type |
||||
|
}, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.replaceTextRange([ |
||||
|
initial.range[0], |
||||
|
result.range[1] |
||||
|
], name); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether a destructured assignment is unnecessarily renamed |
||||
|
* @param {ASTNode} node - node to check |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkDestructured(node) { |
||||
|
var properties, |
||||
|
i; |
||||
|
|
||||
|
if (ignoreDestructuring) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
properties = node.properties; |
||||
|
|
||||
|
for (i = 0; i < properties.length; i++) { |
||||
|
if (properties[i].shorthand) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* If an ObjectPattern property is computed, we have no idea |
||||
|
* if a rename is useless or not. If an ObjectPattern property |
||||
|
* lacks a key, it is likely an ExperimentalRestProperty and |
||||
|
* so there is no "renaming" occurring here. |
||||
|
*/ |
||||
|
if (properties[i].computed || !properties[i].key) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (properties[i].key.type === "Identifier" && properties[i].key.name === properties[i].value.name || |
||||
|
properties[i].key.type === "Literal" && properties[i].key.value === properties[i].value.name) { |
||||
|
reportError(properties[i], properties[i].key, properties[i].value, "Destructuring assignment"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether an import is unnecessarily renamed |
||||
|
* @param {ASTNode} node - node to check |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkImport(node) { |
||||
|
if (ignoreImport) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (node.imported.name === node.local.name && |
||||
|
node.imported.range[0] !== node.local.range[0]) { |
||||
|
reportError(node, node.imported, node.local, "Import"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether an export is unnecessarily renamed |
||||
|
* @param {ASTNode} node - node to check |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkExport(node) { |
||||
|
if (ignoreExport) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (node.local.name === node.exported.name && |
||||
|
node.local.range[0] !== node.exported.range[0]) { |
||||
|
reportError(node, node.local, node.exported, "Export"); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
// Public
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
|
||||
|
return { |
||||
|
ObjectPattern: checkDestructured, |
||||
|
ImportSpecifier: checkImport, |
||||
|
ExportSpecifier: checkExport |
||||
|
}; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,209 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to require or disallow line breaks inside braces. |
||||
|
* @author Toru Nagashima |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var astUtils = require("../ast-utils"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
// Schema objects.
|
||||
|
var OPTION_VALUE = { |
||||
|
oneOf: [ |
||||
|
{ |
||||
|
enum: ["always", "never"] |
||||
|
}, |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
multiline: { |
||||
|
type: "boolean" |
||||
|
}, |
||||
|
minProperties: { |
||||
|
type: "integer", |
||||
|
minimum: 0 |
||||
|
} |
||||
|
}, |
||||
|
additionalProperties: false, |
||||
|
minProperties: 1 |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Normalizes a given option value. |
||||
|
* |
||||
|
* @param {string|object|undefined} value - An option value to parse. |
||||
|
* @returns {{multiline: boolean, minProperties: number}} Normalized option object. |
||||
|
*/ |
||||
|
function normalizeOptionValue(value) { |
||||
|
var multiline = false; |
||||
|
var minProperties = Number.POSITIVE_INFINITY; |
||||
|
|
||||
|
if (value) { |
||||
|
if (value === "always") { |
||||
|
minProperties = 0; |
||||
|
} else if (value === "never") { |
||||
|
minProperties = Number.POSITIVE_INFINITY; |
||||
|
} else { |
||||
|
multiline = Boolean(value.multiline); |
||||
|
minProperties = value.minProperties || Number.POSITIVE_INFINITY; |
||||
|
} |
||||
|
} else { |
||||
|
multiline = true; |
||||
|
} |
||||
|
|
||||
|
return {multiline: multiline, minProperties: minProperties}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Normalizes a given option value. |
||||
|
* |
||||
|
* @param {string|object|undefined} options - An option value to parse. |
||||
|
* @returns {{ObjectExpression: {multiline: boolean, minProperties: number}, ObjectPattern: {multiline: boolean, minProperties: number}}} Normalized option object. |
||||
|
*/ |
||||
|
function normalizeOptions(options) { |
||||
|
if (options && (options.ObjectExpression || options.ObjectPattern)) { |
||||
|
return { |
||||
|
ObjectExpression: normalizeOptionValue(options.ObjectExpression), |
||||
|
ObjectPattern: normalizeOptionValue(options.ObjectPattern) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
var value = normalizeOptionValue(options); |
||||
|
|
||||
|
return {ObjectExpression: value, ObjectPattern: value}; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "enforce consistent line breaks inside braces", |
||||
|
category: "Stylistic Issues", |
||||
|
recommended: false |
||||
|
}, |
||||
|
fixable: "whitespace", |
||||
|
schema: [ |
||||
|
{ |
||||
|
oneOf: [ |
||||
|
OPTION_VALUE, |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
ObjectExpression: OPTION_VALUE, |
||||
|
ObjectPattern: OPTION_VALUE |
||||
|
}, |
||||
|
additionalProperties: false, |
||||
|
minProperties: 1 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var sourceCode = context.getSourceCode(); |
||||
|
var normalizedOptions = normalizeOptions(context.options[0]); |
||||
|
|
||||
|
/** |
||||
|
* Reports a given node if it violated this rule. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node. |
||||
|
* @param {{multiline: boolean, minProperties: number}} options - An option object. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function check(node) { |
||||
|
var options = normalizedOptions[node.type]; |
||||
|
var openBrace = sourceCode.getFirstToken(node); |
||||
|
var closeBrace = sourceCode.getLastToken(node); |
||||
|
var first = sourceCode.getTokenOrCommentAfter(openBrace); |
||||
|
var last = sourceCode.getTokenOrCommentBefore(closeBrace); |
||||
|
var needsLinebreaks = ( |
||||
|
node.properties.length >= options.minProperties || |
||||
|
( |
||||
|
options.multiline && |
||||
|
node.properties.length > 0 && |
||||
|
first.loc.start.line !== last.loc.end.line |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
/* |
||||
|
* Use tokens or comments to check multiline or not. |
||||
|
* But use only tokens to check whether line breaks are needed. |
||||
|
* This allows: |
||||
|
* var obj = { // eslint-disable-line foo
|
||||
|
* a: 1 |
||||
|
* } |
||||
|
*/ |
||||
|
first = sourceCode.getTokenAfter(openBrace); |
||||
|
last = sourceCode.getTokenBefore(closeBrace); |
||||
|
|
||||
|
if (needsLinebreaks) { |
||||
|
if (astUtils.isTokenOnSameLine(openBrace, first)) { |
||||
|
context.report({ |
||||
|
message: "Expected a line break after this open brace.", |
||||
|
node: node, |
||||
|
loc: openBrace.loc.start, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.insertTextAfter(openBrace, "\n"); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if (astUtils.isTokenOnSameLine(last, closeBrace)) { |
||||
|
context.report({ |
||||
|
message: "Expected a line break before this close brace.", |
||||
|
node: node, |
||||
|
loc: closeBrace.loc.start, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.insertTextBefore(closeBrace, "\n"); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} else { |
||||
|
if (!astUtils.isTokenOnSameLine(openBrace, first)) { |
||||
|
context.report({ |
||||
|
message: "Unexpected a line break after this open brace.", |
||||
|
node: node, |
||||
|
loc: openBrace.loc.start, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.removeRange([ |
||||
|
openBrace.range[1], |
||||
|
first.range[0] |
||||
|
]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if (!astUtils.isTokenOnSameLine(last, closeBrace)) { |
||||
|
context.report({ |
||||
|
message: "Unexpected a line break before this close brace.", |
||||
|
node: node, |
||||
|
loc: closeBrace.loc.start, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.removeRange([ |
||||
|
last.range[1], |
||||
|
closeBrace.range[0] |
||||
|
]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
ObjectExpression: check, |
||||
|
ObjectPattern: check |
||||
|
}; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to enforce placing object properties on separate lines. |
||||
|
* @author Vitor Balocco |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "enforce placing object properties on separate lines", |
||||
|
category: "Stylistic Issues", |
||||
|
recommended: false |
||||
|
}, |
||||
|
|
||||
|
schema: [ |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
allowMultiplePropertiesPerLine: { |
||||
|
type: "boolean" |
||||
|
} |
||||
|
}, |
||||
|
additionalProperties: false |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var allowSameLine = context.options[0] && Boolean(context.options[0].allowMultiplePropertiesPerLine); |
||||
|
var errorMessage = allowSameLine ? |
||||
|
"Object properties must go on a new line if they aren't all on the same line" : |
||||
|
"Object properties must go on a new line"; |
||||
|
|
||||
|
var sourceCode = context.getSourceCode(); |
||||
|
|
||||
|
return { |
||||
|
ObjectExpression: function(node) { |
||||
|
var lastTokenOfPreviousProperty, firstTokenOfCurrentProperty; |
||||
|
|
||||
|
if (allowSameLine) { |
||||
|
if (node.properties.length > 1) { |
||||
|
var firstTokenOfFirstProperty = sourceCode.getFirstToken(node.properties[0]); |
||||
|
var lastTokenOfLastProperty = sourceCode.getLastToken(node.properties[node.properties.length - 1]); |
||||
|
|
||||
|
if (firstTokenOfFirstProperty.loc.end.line === lastTokenOfLastProperty.loc.start.line) { |
||||
|
|
||||
|
// All keys and values are on the same line
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (var i = 1; i < node.properties.length; i++) { |
||||
|
lastTokenOfPreviousProperty = sourceCode.getLastToken(node.properties[i - 1]); |
||||
|
firstTokenOfCurrentProperty = sourceCode.getFirstToken(node.properties[i]); |
||||
|
|
||||
|
if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) { |
||||
|
context.report({ |
||||
|
node: node, |
||||
|
loc: firstTokenOfCurrentProperty.loc.start, |
||||
|
message: errorMessage |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,107 @@ |
|||||
|
/** |
||||
|
* @fileoverview Enforce spacing between rest and spread operators and their expressions. |
||||
|
* @author Kai Cataldo |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
meta: { |
||||
|
docs: { |
||||
|
description: "enforce spacing between rest and spread operators and their expressions", |
||||
|
category: "ECMAScript 6", |
||||
|
recommended: false |
||||
|
}, |
||||
|
fixable: "whitespace", |
||||
|
schema: [ |
||||
|
{ |
||||
|
enum: ["always", "never"] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
create: function(context) { |
||||
|
var sourceCode = context.getSourceCode(), |
||||
|
alwaysSpace = context.options[0] === "always"; |
||||
|
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Checks whitespace between rest/spread operators and their expressions |
||||
|
* @param {ASTNode} node - The node to check |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkWhiteSpace(node) { |
||||
|
var operator = sourceCode.getFirstToken(node), |
||||
|
nextToken = sourceCode.getTokenAfter(operator), |
||||
|
hasWhitespace = sourceCode.isSpaceBetweenTokens(operator, nextToken), |
||||
|
type; |
||||
|
|
||||
|
switch (node.type) { |
||||
|
case "SpreadElement": |
||||
|
type = "spread"; |
||||
|
break; |
||||
|
case "RestElement": |
||||
|
type = "rest"; |
||||
|
break; |
||||
|
case "ExperimentalSpreadProperty": |
||||
|
type = "spread property"; |
||||
|
break; |
||||
|
case "ExperimentalRestProperty": |
||||
|
type = "rest property"; |
||||
|
break; |
||||
|
default: |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (alwaysSpace && !hasWhitespace) { |
||||
|
context.report({ |
||||
|
node: node, |
||||
|
loc: { |
||||
|
line: operator.loc.end.line, |
||||
|
column: operator.loc.end.column |
||||
|
}, |
||||
|
message: "Expected whitespace after {{type}} operator", |
||||
|
data: { |
||||
|
type: type |
||||
|
}, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.replaceTextRange([operator.range[1], nextToken.range[0]], " "); |
||||
|
} |
||||
|
}); |
||||
|
} else if (!alwaysSpace && hasWhitespace) { |
||||
|
context.report({ |
||||
|
node: node, |
||||
|
loc: { |
||||
|
line: operator.loc.end.line, |
||||
|
column: operator.loc.end.column |
||||
|
}, |
||||
|
message: "Unexpected whitespace after {{type}} operator", |
||||
|
data: { |
||||
|
type: type |
||||
|
}, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.removeRange([operator.range[1], nextToken.range[0]]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
// Public
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
|
||||
|
return { |
||||
|
SpreadElement: checkWhiteSpace, |
||||
|
RestElement: checkWhiteSpace, |
||||
|
ExperimentalSpreadProperty: checkWhiteSpace, |
||||
|
ExperimentalRestProperty: checkWhiteSpace |
||||
|
}; |
||||
|
} |
||||
|
}; |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue