Comparing version 0.20.0 to 0.21.0
#!/usr/bin/env node | ||
var concat = require("concat-stream"), | ||
configInit = require("../lib/config-initializer"), | ||
cli = require("../lib/cli"); | ||
var exitCode = 0, | ||
useStdIn = (process.argv.indexOf("--stdin") > -1); | ||
useStdIn = (process.argv.indexOf("--stdin") > -1), | ||
init = (process.argv.indexOf("--init") > -1); | ||
@@ -18,2 +20,13 @@ if (useStdIn) { | ||
})); | ||
} else if (init) { | ||
configInit.initializeConfig(function(err) { | ||
if (err) { | ||
exitCode = 1; | ||
console.error(err.message); | ||
console.error(err.stack); | ||
} else { | ||
console.log("Successfully created .eslintrc file in " + process.cwd()); | ||
exitCode = 0; | ||
} | ||
}); | ||
} else { | ||
@@ -20,0 +33,0 @@ exitCode = cli.execute(process.argv); |
@@ -59,2 +59,3 @@ { | ||
"no-mixed-spaces-and-tabs": [2, false], | ||
"linebreak-style": [0, "unix"], | ||
"no-multi-spaces": 2, | ||
@@ -101,2 +102,3 @@ "no-multi-str": 2, | ||
"no-underscore-dangle": 2, | ||
"no-unneeded-ternary": 0, | ||
"no-unreachable": 2, | ||
@@ -123,2 +125,3 @@ "no-unused-expressions": 2, | ||
"default-case": 0, | ||
"dot-location": 0, | ||
"dot-notation": [2, { "allowKeywords": true }], | ||
@@ -125,0 +128,0 @@ "eol-last": 2, |
@@ -243,2 +243,23 @@ /** | ||
/** | ||
* Returns result with warning by ignore settings | ||
* @param {string} filePath File path of checked code | ||
* @returns {Result} Result with single warning | ||
* @private | ||
*/ | ||
function createIgnoreResult(filePath) { | ||
return { | ||
filePath: filePath, | ||
messages: [ | ||
{ | ||
fatal: false, | ||
severity: 1, | ||
message: "File ignored because of your .eslintignore file. Use --no-ignore to override." | ||
} | ||
], | ||
errorCount: 0, | ||
warningCount: 1 | ||
}; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -268,4 +289,2 @@ // Public Interface | ||
} | ||
loadPlugins(this.options.plugins); | ||
} | ||
@@ -278,2 +297,16 @@ | ||
/** | ||
* Add a plugin by passing it's configuration | ||
* @param {string} name Name of the plugin. | ||
* @param {Object} pluginobject Plugin configuration object. | ||
* @returns {void} | ||
*/ | ||
addPlugin: function(name, pluginobject) { | ||
var pluginNameWithoutPrefix = util.removePluginPrefix(util.removeNameSpace(name)); | ||
if (pluginobject.rules) { | ||
rules.import(pluginobject.rules, pluginNameWithoutPrefix); | ||
} | ||
loadedPlugins[pluginNameWithoutPrefix] = pluginobject; | ||
}, | ||
/** | ||
* Executes the current configuration on an array of file and directory names. | ||
@@ -309,14 +342,3 @@ * @param {string[]} files An array of file and directory names. | ||
if (fs.statSync(path.resolve(file)).isFile() && processed.indexOf(file) === -1) { | ||
results.push({ | ||
filePath: file, | ||
messages: [ | ||
{ | ||
fatal: false, | ||
severity: 1, | ||
message: "File ignored because of your .eslintignore file. Use --no-ignore to override." | ||
} | ||
], | ||
errorCount: 0, | ||
warningCount: 1 | ||
}); | ||
results.push(createIgnoreResult(file)); | ||
} | ||
@@ -345,5 +367,13 @@ }); | ||
results = [], | ||
stats; | ||
stats, | ||
options = this.options, | ||
ignoredPaths = IgnoredPaths.load(options), | ||
exclude = ignoredPaths.contains.bind(ignoredPaths); | ||
results.push(processText(text, configHelper, filename)); | ||
if (filename && options.ignore && exclude(filename)) { | ||
results.push(createIgnoreResult(filename)); | ||
} else { | ||
results.push(processText(text, configHelper, filename)); | ||
} | ||
stats = calculateStatsPerRun(results); | ||
@@ -422,4 +452,2 @@ | ||
} | ||
} | ||
@@ -426,0 +454,0 @@ |
@@ -23,3 +23,4 @@ /** | ||
yaml = require("js-yaml"), | ||
userHome = require("user-home"); | ||
userHome = require("user-home"), | ||
isAbsolutePath = require("path-is-absolute"); | ||
@@ -48,5 +49,18 @@ //------------------------------------------------------------------------------ | ||
/** | ||
* Determines if a given string represents a filepath or not using the same | ||
* conventions as require(), meaning that the first character must be nonalphanumeric | ||
* to be considered a file path. | ||
* @param {string} filePath The string to check. | ||
* @returns {boolean} True if it's a filepath, false if not. | ||
* @private | ||
*/ | ||
function isFilePath(filePath) { | ||
return isAbsolutePath(filePath) || !/\w/.test(filePath[0]); | ||
} | ||
/** | ||
* Load and parse a JSON config object from a file. | ||
* @param {string} filePath the path to the JSON config file | ||
* @returns {Object} the parsed config object (empty object if there was a parse error) | ||
* @private | ||
*/ | ||
@@ -57,11 +71,52 @@ function loadConfig(filePath) { | ||
if (filePath) { | ||
try { | ||
config = yaml.safeLoad(stripComments(fs.readFileSync(filePath, "utf8"))) || {}; | ||
} catch (e) { | ||
debug("Error reading YAML file: " + filePath); | ||
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; | ||
throw e; | ||
if (isFilePath(filePath)) { | ||
try { | ||
config = yaml.safeLoad(stripComments(fs.readFileSync(filePath, "utf8"))) || {}; | ||
} catch (e) { | ||
debug("Error reading YAML file: " + filePath); | ||
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; | ||
throw e; | ||
} | ||
} else { | ||
// it's a package | ||
if (filePath.indexOf("eslint-config-") === -1) { | ||
filePath = "eslint-config-" + filePath; | ||
} | ||
config = require(filePath); | ||
} | ||
// If an `extends` property is defined, it represents a configuration file to use as | ||
// a "parent". Load the referenced file and merge the configuration recursively. | ||
if (config.extends) { | ||
var parentPath = config.extends; | ||
if (isFilePath(parentPath)) { | ||
// If the `extends` path is relative, use the directory of the current configuration | ||
// file as the reference point. Otherwise, use as-is. | ||
parentPath = (!isAbsolutePath(parentPath) ? | ||
path.join(path.dirname(filePath), parentPath) : | ||
parentPath | ||
); | ||
} | ||
// Merge the configuration, ensuring children get preference over the parent | ||
try { | ||
config = util.mergeConfigs(loadConfig(parentPath), config); | ||
} catch (e) { | ||
// If the file referenced by `extends` failed to load, add the path to the | ||
// configuration file that referenced it to the error message so the user is | ||
// able to see where it was referenced from, then re-throw | ||
e.message += "\nReferenced from: " + filePath; | ||
throw e; | ||
} | ||
} | ||
} | ||
return config; | ||
@@ -153,2 +208,3 @@ } | ||
debug("Loading " + localConfigFile); | ||
localConfig = loadConfig(localConfigFile); | ||
@@ -249,3 +305,2 @@ | ||
this.baseConfig.format = options.format; | ||
this.useEslintrc = (options.useEslintrc !== false); | ||
@@ -252,0 +307,0 @@ |
@@ -961,3 +961,3 @@ /** | ||
if (["FunctionDeclaration", "FunctionExpression", | ||
"ArrowFunctionExpression"].indexOf(current.type) >= 0) { | ||
"ArrowFunctionExpression", "SwitchStatement"].indexOf(current.type) >= 0) { | ||
parents.push(current); | ||
@@ -964,0 +964,0 @@ } |
@@ -36,2 +36,11 @@ /** | ||
/** | ||
* Check if the token is an punctuator with a value of curly brace | ||
* @param {object} token - Token to check | ||
* @returns {boolean} true if its a curly punctuator | ||
* @private | ||
*/ | ||
function isCurlyPunctuator(token) { | ||
return token.value === "{" || token.value === "}"; | ||
} | ||
@@ -101,3 +110,3 @@ /** | ||
if (style === "1tbs") { | ||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { | ||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line && isCurlyPunctuator(tokens[0]) ) { | ||
context.report(node.alternate, CLOSE_MESSAGE); | ||
@@ -104,0 +113,0 @@ } |
@@ -51,3 +51,3 @@ /** | ||
function checkFunction(node) { | ||
var isMethod, starToken, tokenBefore, tokenAfter; | ||
var prevToken, starToken, nextToken; | ||
@@ -58,5 +58,3 @@ if (!node.generator) { | ||
isMethod = !!context.getAncestors().pop().method; | ||
if (isMethod) { | ||
if (node.parent.method || node.parent.type === "MethodDefinition") { | ||
starToken = context.getTokenBefore(node, 1); | ||
@@ -68,11 +66,11 @@ } else { | ||
// Only check before when preceded by `function` keyword | ||
tokenBefore = context.getTokenBefore(starToken); | ||
if (tokenBefore.value === "function") { | ||
checkSpacing("before", tokenBefore, starToken); | ||
prevToken = context.getTokenBefore(starToken); | ||
if (prevToken.value === "function" || prevToken.value === "static") { | ||
checkSpacing("before", prevToken, starToken); | ||
} | ||
// Only check after when followed by an identifier | ||
tokenAfter = context.getTokenAfter(starToken); | ||
if (tokenAfter.type === "Identifier") { | ||
checkSpacing("after", starToken, tokenAfter); | ||
nextToken = context.getTokenAfter(starToken); | ||
if (nextToken.type === "Identifier") { | ||
checkSpacing("after", starToken, nextToken); | ||
} | ||
@@ -79,0 +77,0 @@ } |
@@ -38,3 +38,3 @@ /** | ||
function continuesPropertyGroup(lastMember, candidate) { | ||
var groupEndLine = lastMember.loc.end.line, | ||
var groupEndLine = lastMember.loc.start.line, | ||
candidateStartLine = candidate.loc.start.line, | ||
@@ -41,0 +41,0 @@ comments, i; |
@@ -70,4 +70,4 @@ /** | ||
var config = context.options[0] || {}; | ||
config.newIsCap = config.newIsCap === false ? false : true; | ||
config.capIsNew = config.capIsNew === false ? false : true; | ||
config.newIsCap = config.newIsCap !== false; | ||
config.capIsNew = config.capIsNew !== false; | ||
@@ -74,0 +74,0 @@ var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); |
@@ -24,2 +24,12 @@ /** | ||
var config = context.options[0] || {}; | ||
var exceptions = config.exceptions || []; | ||
var modifiedBuiltins = BUILTINS; | ||
if (exceptions.length) { | ||
modifiedBuiltins = BUILTINS.filter(function(builtIn) { | ||
return exceptions.indexOf(builtIn) === -1; | ||
}); | ||
} | ||
return { | ||
@@ -43,3 +53,3 @@ | ||
BUILTINS.forEach(function(builtin) { | ||
modifiedBuiltins.forEach(function(builtin) { | ||
if (lhs.object.object.name === builtin) { | ||
@@ -69,3 +79,3 @@ context.report(node, builtin + " prototype is read only, properties should not be added."); | ||
object.type === "Identifier" && | ||
(BUILTINS.indexOf(object.name) > -1) && | ||
(modifiedBuiltins.indexOf(object.name) > -1) && | ||
subject.property.name === "prototype") { | ||
@@ -72,0 +82,0 @@ |
@@ -15,3 +15,4 @@ /** | ||
var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000]+/mg; | ||
var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg, | ||
irregularLineTerminators = /[\u2028\u2029]/mg; | ||
@@ -34,3 +35,3 @@ // Module store of errors that we have found | ||
if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) { | ||
if (errorLoc.column >= locStart.column && errorLoc.column <= locEnd.column) { | ||
if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) { | ||
return false; | ||
@@ -44,3 +45,3 @@ } | ||
/** | ||
* Checks nodes for errors that we are choosing to ignore and calls the relevent methods to remove the errors | ||
* Checks nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors | ||
* @param {ASTNode} node to check for matching errors. | ||
@@ -53,3 +54,3 @@ * @returns {void} | ||
// If we have irregular characters remove them from the errors list | ||
if (node.value.match(irregularWhitespace)) { | ||
if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) { | ||
removeStringError(node); | ||
@@ -60,2 +61,55 @@ } | ||
/** | ||
* Checks the program source for irregular whitespace | ||
* @param {ASTNode} node The program node | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function checkForIrregularWhitespace(node) { | ||
var sourceLines = context.getSourceLines(); | ||
sourceLines.forEach(function (sourceLine, lineIndex) { | ||
var lineNumber = lineIndex + 1, | ||
location, | ||
match; | ||
while ((match = irregularWhitespace.exec(sourceLine)) !== null) { | ||
location = { | ||
line: lineNumber, | ||
column: match.index | ||
}; | ||
errors.push([node, location, "Irregular whitespace not allowed"]); | ||
} | ||
}); | ||
} | ||
/** | ||
* Checks the program source for irregular line terminators | ||
* @param {ASTNode} node The program node | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function checkForIrregularLineTerminators(node) { | ||
var source = context.getSource(), | ||
sourceLines = context.getSourceLines(), | ||
linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g), | ||
lastLineIndex = -1, | ||
lineIndex, | ||
location, | ||
match; | ||
while ((match = irregularLineTerminators.exec(source)) !== null) { | ||
lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; | ||
location = { | ||
line: lineIndex + 1, | ||
column: sourceLines[lineIndex].length | ||
}; | ||
errors.push([node, location, "Irregular whitespace not allowed"]); | ||
lastLineIndex = lineIndex; | ||
} | ||
} | ||
return { | ||
@@ -69,22 +123,9 @@ "Program": function (node) { | ||
*/ | ||
var sourceLines = context.getSourceLines(); | ||
sourceLines.forEach(function (sourceLine, lineIndex) { | ||
var location, | ||
match = irregularWhitespace.exec(sourceLine); | ||
checkForIrregularWhitespace(node); | ||
checkForIrregularLineTerminators(node); | ||
}, | ||
if (match !== null) { | ||
location = { | ||
line: lineIndex + 1, | ||
column: match.index | ||
}; | ||
errors.push([node, location, "Irregular whitespace not allowed"]); | ||
} | ||
}); | ||
}, | ||
"Identifier": removeInvalidNodeErrors, | ||
"Literal": removeInvalidNodeErrors, | ||
"Statement": removeInvalidNodeErrors, | ||
"Expression": removeInvalidNodeErrors, | ||
"Program:exit": function () { | ||
@@ -91,0 +132,0 @@ |
@@ -14,20 +14,3 @@ /** | ||
module.exports = function(context) { | ||
var loopNodeTypes = [ | ||
"ForStatement", | ||
"WhileStatement", | ||
"ForInStatement", | ||
"ForOfStatement", | ||
"DoWhileStatement" | ||
]; | ||
/** | ||
* Checks if the given node is a loop. | ||
* @param {ASTNode} node The AST node to check. | ||
* @returns {boolean} Whether or not the node is a loop. | ||
*/ | ||
function isLoop(node) { | ||
return loopNodeTypes.indexOf(node.type) > -1; | ||
} | ||
/** | ||
* Reports if the given node has an ancestor node which is a loop. | ||
@@ -40,3 +23,27 @@ * @param {ASTNode} node The AST node to check. | ||
if (ancestors.some(isLoop)) { | ||
/** | ||
* Checks if the given node is a loop and current context is in the loop. | ||
* @param {ASTNode} ancestor The AST node to check. | ||
* @param {number} index The index of ancestor in ancestors. | ||
* @returns {boolean} Whether or not the node is a loop and current context is in the loop. | ||
*/ | ||
function isInLoop(ancestor, index) { | ||
switch (ancestor.type) { | ||
case "ForStatement": | ||
return ancestor.init !== ancestors[index + 1]; | ||
case "ForInStatement": | ||
case "ForOfStatement": | ||
return ancestor.right !== ancestors[index + 1]; | ||
case "WhileStatement": | ||
case "DoWhileStatement": | ||
return true; | ||
default: | ||
return false; | ||
} | ||
} | ||
if (ancestors.some(isInLoop)) { | ||
context.report(node, "Don't make functions within a loop"); | ||
@@ -43,0 +50,0 @@ } |
@@ -37,5 +37,12 @@ /** | ||
}); | ||
// swallow the final newline, as some editors add it automatically | ||
// and we don't want it to cause an issue | ||
if (trimmedLines[trimmedLines.length - 1] === "") { | ||
trimmedLines = trimmedLines.slice(0, -1); | ||
} | ||
// Aggregate and count blank lines | ||
do { | ||
lastLocation = currentLocation; | ||
currentLocation = trimmedLines.indexOf("", currentLocation + 1); | ||
while (currentLocation !== -1) { | ||
lastLocation = currentLocation; | ||
@@ -57,3 +64,3 @@ currentLocation = trimmedLines.indexOf("", currentLocation + 1); | ||
} | ||
} while (currentLocation !== -1); | ||
} | ||
} | ||
@@ -60,0 +67,0 @@ }; |
@@ -14,3 +14,3 @@ /** | ||
var nativeObjects = ["Array", "Boolean", "Date", "decodeURI", | ||
var NATIVE_OBJECTS = ["Array", "Boolean", "Date", "decodeURI", | ||
"decodeURIComponent", "encodeURI", "encodeURIComponent", | ||
@@ -22,7 +22,16 @@ "Error", "eval", "EvalError", "Function", "isFinite", | ||
"Map", "NaN", "Set", "WeakMap", "Infinity", "undefined"]; | ||
var config = context.options[0] || {}; | ||
var exceptions = config.exceptions || []; | ||
var modifiedNativeObjects = NATIVE_OBJECTS; | ||
if (exceptions.length) { | ||
modifiedNativeObjects = NATIVE_OBJECTS.filter(function(builtIn) { | ||
return exceptions.indexOf(builtIn) === -1; | ||
}); | ||
} | ||
return { | ||
"AssignmentExpression": function(node) { | ||
if (nativeObjects.indexOf(node.left.name) >= 0) { | ||
if (modifiedNativeObjects.indexOf(node.left.name) >= 0) { | ||
context.report(node, node.left.name + " is a read-only native object."); | ||
@@ -33,3 +42,3 @@ } | ||
"VariableDeclarator": function(node) { | ||
if (nativeObjects.indexOf(node.id.name) >= 0) { | ||
if (modifiedNativeObjects.indexOf(node.id.name) >= 0) { | ||
context.report(node, "Redefinition of '{{nativeObject}}'.", { nativeObject: node.id.name }); | ||
@@ -36,0 +45,0 @@ } |
@@ -22,3 +22,2 @@ /** | ||
scope.variables.forEach(function(variable) { | ||
if (variable.identifiers && variable.identifiers.length > 1) { | ||
@@ -54,7 +53,16 @@ variable.identifiers.sort(function(a, b) { | ||
return { | ||
"Program": findVariables, | ||
"FunctionExpression": findVariables, | ||
"FunctionDeclaration": findVariables | ||
}; | ||
if (context.ecmaFeatures.blockBindings) { | ||
return { | ||
"Program": findVariables, | ||
"BlockStatement": findVariables, | ||
"SwitchStatement": findVariables | ||
}; | ||
} else { | ||
return { | ||
"Program": findVariables, | ||
"FunctionDeclaration": findVariables, | ||
"FunctionExpression": findVariables, | ||
"ArrowFunctionExpression": findVariables | ||
}; | ||
} | ||
}; |
@@ -13,4 +13,9 @@ /** | ||
var TRAILER = "[ \t\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]+$"; | ||
var BLANK_PREFIX = "[^( |\t)]", | ||
TRAILER = "[ \t\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]$"; | ||
var options = context.options[0] || {}, | ||
skipBlankLines = options.skipBlankLines || false; | ||
//-------------------------------------------------------------------------- | ||
@@ -28,3 +33,3 @@ // Public | ||
var src = context.getSource(), | ||
re = new RegExp(TRAILER, "mg"), | ||
re = new RegExp((skipBlankLines ? BLANK_PREFIX : "") + TRAILER, "mg"), | ||
match, lines, location; | ||
@@ -31,0 +36,0 @@ |
@@ -30,5 +30,10 @@ /** | ||
//-------------------------------------------------------------------------- | ||
// Helpers | ||
//-------------------------------------------------------------------------- | ||
/** | ||
* Determines if a given variable is being exported from a module. | ||
* @param {Variable} variable EScope variable object. | ||
* @param {Variable} variable - EScope variable object. | ||
* @returns {boolean} True if the variable is exported, false if not. | ||
@@ -56,3 +61,3 @@ * @private | ||
* Determines if a reference is a read operation. | ||
* @param {Reference} ref - an escope Reference | ||
* @param {Reference} ref - An escope Reference | ||
* @returns {Boolean} whether the given reference represents a read operation | ||
@@ -66,11 +71,17 @@ * @private | ||
/** | ||
* Determine if an identifier is referencing the enclosing function name. | ||
* @param {Reference} ref The reference to check. | ||
* Determine if an identifier is referencing an enclosing function name. | ||
* @param {Reference} ref - The reference to check. | ||
* @param {ASTNode[]} nodes - The candidate function nodes. | ||
* @returns {boolean} True if it's a self-reference, false if not. | ||
* @private | ||
*/ | ||
function isSelfReference(ref) { | ||
function isSelfReference(ref, nodes) { | ||
var scope = ref.from; | ||
if (ref.from.type === "function" && ref.from.block.id) { | ||
return ref.identifier.name === ref.from.block.id.name; | ||
while (scope != null) { | ||
if (nodes.indexOf(scope.block) >= 0) { | ||
return true; | ||
} | ||
scope = scope.upper; | ||
} | ||
@@ -82,11 +93,22 @@ | ||
/** | ||
* Determines if a reference should be counted as a read. A reference should | ||
* be counted only if it's a read and it's not a reference to the containing | ||
* function declaration name. | ||
* @param {Reference} ref The reference to check. | ||
* @returns {boolean} True if it's a value read reference, false if not. | ||
* @private | ||
* Determines if the variable is used. | ||
* @param {Variable} variable - The variable to check. | ||
* @param {Reference[]} [references=variable.references] - The variable references to check. | ||
* @returns {boolean} True if the variable is used | ||
*/ | ||
function isValidReadRef(ref) { | ||
return isReadRef(ref) && !isSelfReference(ref); | ||
function isUsedVariable(variable, references) { | ||
var functionNodes = variable.defs.filter(function (def) { | ||
return def.type === "FunctionName"; | ||
}).map(function (def) { | ||
return def.node; | ||
}), | ||
isFunctionDefinition = functionNodes.length > 0; | ||
if (!references) { | ||
references = variable.references; | ||
} | ||
return references.some(function (ref) { | ||
return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes)); | ||
}); | ||
} | ||
@@ -139,3 +161,3 @@ | ||
if (variables[i].references.filter(isValidReadRef).length === 0 && !isExported(variables[i])) { | ||
if (!isUsedVariable(variables[i]) && !isExported(variables[i])) { | ||
unused.push(variables[i]); | ||
@@ -149,2 +171,6 @@ } | ||
//-------------------------------------------------------------------------- | ||
// Public | ||
//-------------------------------------------------------------------------- | ||
return { | ||
@@ -158,11 +184,30 @@ "Program:exit": function(programNode) { | ||
if (config.vars === "all") { | ||
var unresolvedRefs = globalScope.through.filter(isValidReadRef).map(function(ref) { | ||
return ref.identifier.name; | ||
}); | ||
var ref, name; | ||
// Avoid inherited properties that could induce false positives (e.g. "constructor") | ||
var unresolvedRefs = Object.create(null); | ||
// Search for read references, and store them in a dictionary by name | ||
for (i = 0, l = globalScope.through.length; i < l; ++i) { | ||
ref = globalScope.through[i]; | ||
name = ref.identifier.name; | ||
if (isReadRef(ref)) { | ||
if (!unresolvedRefs[name]) { | ||
unresolvedRefs[name] = []; | ||
} | ||
unresolvedRefs[name].push(ref); | ||
} | ||
} | ||
for (i = 0, l = globalScope.variables.length; i < l; ++i) { | ||
if (unresolvedRefs.indexOf(globalScope.variables[i].name) < 0 && | ||
!globalScope.variables[i].eslintUsed && !isExported(globalScope.variables[i])) { | ||
name = globalScope.variables[i].name; | ||
var isUsed = unresolvedRefs[name] && | ||
isUsedVariable(globalScope.variables[i], unresolvedRefs[name]); | ||
if (!isUsed && !globalScope.variables[i].eslintUsed && | ||
!isExported(globalScope.variables[i])) { | ||
unused.push(globalScope.variables[i]); | ||
} | ||
} | ||
@@ -169,0 +214,0 @@ } |
@@ -21,2 +21,9 @@ /** | ||
/** | ||
* Finds variable declarations in a given scope. | ||
* @param {string} name The variable name to find. | ||
* @param {Scope} scope The scope to search in. | ||
* @returns {Object} The variable declaration object. | ||
* @private | ||
*/ | ||
function findDeclaration(name, scope) { | ||
@@ -35,4 +42,9 @@ // try searching in the current scope first | ||
function findVariables() { | ||
var scope = context.getScope(); | ||
/** | ||
* Finds and validates all variables in a given scope. | ||
* @param {Scope} scope The scope object. | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function findVariablesInScope(scope) { | ||
var typeOption = context.options[0]; | ||
@@ -63,4 +75,24 @@ | ||
/** | ||
* Validates variables inside of a node's scope. | ||
* @param {ASTNode} node The node to check. | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function findVariables() { | ||
var scope = context.getScope(); | ||
findVariablesInScope(scope); | ||
} | ||
return { | ||
"Program": findVariables, | ||
"Program": function() { | ||
var scope = context.getScope(); | ||
findVariablesInScope(scope); | ||
// both Node.js and Modules have an extra scope | ||
if (context.ecmaFeatures.globalReturn || context.ecmaFeatures.modules) { | ||
findVariablesInScope(scope.childScopes[0]); | ||
} | ||
}, | ||
"FunctionExpression": findVariables, | ||
@@ -67,0 +99,0 @@ "FunctionDeclaration": findVariables, |
@@ -22,3 +22,3 @@ /** | ||
function checkFunction(node) { | ||
var previousToken, nextToken; | ||
var previousToken, nextToken, isCall; | ||
@@ -31,9 +31,28 @@ if (node.type === "ArrowFunctionExpression" && | ||
if (!/CallExpression|NewExpression/.test(node.parent.type)) { | ||
previousToken = context.getTokenBefore(node); | ||
nextToken = context.getTokenAfter(node); | ||
if (previousToken.value === "(" && nextToken.value === ")") { | ||
context.report(node, "Wrapping non-IIFE function literals in parens is unnecessary."); | ||
// (function() {}).foo | ||
if (node.parent.type === "MemberExpression" && node.parent.object === node) { | ||
return; | ||
} | ||
// (function(){})() | ||
isCall = /CallExpression|NewExpression/.test(node.parent.type); | ||
if (isCall && node.parent.callee === node) { | ||
return; | ||
} | ||
previousToken = context.getTokenBefore(node); | ||
nextToken = context.getTokenAfter(node); | ||
// f(function(){}) and new f(function(){}) | ||
if (isCall) { | ||
// if the previousToken is right after the callee | ||
if (node.parent.callee.range[1] === previousToken.range[0]) { | ||
return; | ||
} | ||
} | ||
if (previousToken.value === "(" && nextToken.value === ")") { | ||
context.report(node, "Wrapping non-IIFE function literals in parens is unnecessary."); | ||
} | ||
} | ||
@@ -40,0 +59,0 @@ |
@@ -47,14 +47,14 @@ /** | ||
if (node.value.type === "ArrowFunctionExpression" && APPLY_TO_METHODS) { | ||
if (node.value.type === "FunctionExpression" && APPLY_TO_METHODS) { | ||
// {x: ()=>{}} should be written as {x() {}} | ||
context.report(node, "Expected method shorthand."); | ||
} else if (node.value.type === "FunctionExpression" && APPLY_TO_METHODS) { | ||
// {x: function(){}} should be written as {x() {}} | ||
context.report(node, "Expected method shorthand."); | ||
} else if (node.key.name === node.value.name && APPLY_TO_PROPS) { | ||
} else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) { | ||
// {x: x} should be written as {x} | ||
context.report(node, "Expected property shorthand."); | ||
} else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) { | ||
// {"x": x} should be written as {x} | ||
context.report(node, "Expected property shorthand."); | ||
} | ||
@@ -61,0 +61,0 @@ } |
/** | ||
* @fileoverview A rule to ensure the use of a single variable declaration. | ||
* @fileoverview A rule to control the use of single variable declarations. | ||
* @author Ian Christian Myers | ||
* @copyright 2015 Ian VanSchooten. All rights reserved. | ||
* @copyright 2015 Joey Baker. All rights reserved. | ||
@@ -17,12 +18,52 @@ * @copyright 2015 Danny Fritz. All rights reserved. | ||
var MODE = context.options[0] || "always"; | ||
var options = {}; | ||
var MODE_ALWAYS = "always", | ||
MODE_NEVER = "never"; | ||
// simple options configuration with just a string or no option | ||
if (typeof context.options[0] === "string" || context.options[0] == null) { | ||
options.var = MODE; | ||
options.let = MODE; | ||
options.const = MODE; | ||
} else { | ||
options = context.options[0]; | ||
var mode = context.options[0]; | ||
var options = { | ||
}; | ||
if (typeof mode === "string") { // simple options configuration with just a string | ||
options.var = { uninitialized: mode, initialized: mode}; | ||
options.let = { uninitialized: mode, initialized: mode}; | ||
options.const = { uninitialized: mode, initialized: mode}; | ||
} else if (typeof mode === "object") { // options configuration is an object | ||
if (mode.hasOwnProperty("var") && typeof mode.var === "string") { | ||
options.var = { uninitialized: mode.var, initialized: mode.var}; | ||
} | ||
if (mode.hasOwnProperty("let") && typeof mode.let === "string") { | ||
options.let = { uninitialized: mode.let, initialized: mode.let}; | ||
} | ||
if (mode.hasOwnProperty("const") && typeof mode.const === "string") { | ||
options.const = { uninitialized: mode.const, initialized: mode.const}; | ||
} | ||
if (mode.hasOwnProperty("uninitialized")) { | ||
if (!options.var) { | ||
options.var = {}; | ||
} | ||
if (!options.let) { | ||
options.let = {}; | ||
} | ||
if (!options.const) { | ||
options.const = {}; | ||
} | ||
options.var.uninitialized = mode.uninitialized; | ||
options.let.uninitialized = mode.uninitialized; | ||
options.const.uninitialized = mode.uninitialized; | ||
} | ||
if (mode.hasOwnProperty("initialized")) { | ||
if (!options.var) { | ||
options.var = {}; | ||
} | ||
if (!options.let) { | ||
options.let = {}; | ||
} | ||
if (!options.const) { | ||
options.const = {}; | ||
} | ||
options.var.initialized = mode.initialized; | ||
options.let.initialized = mode.initialized; | ||
options.const.initialized = mode.initialized; | ||
} | ||
} | ||
@@ -43,3 +84,6 @@ | ||
function startBlock() { | ||
blockStack.push({let: false, const: false}); | ||
blockStack.push({ | ||
let: {initialized: false, uninitialized: false}, | ||
const: {initialized: false, uninitialized: false} | ||
}); | ||
} | ||
@@ -53,3 +97,3 @@ | ||
function startFunction() { | ||
functionStack.push(false); | ||
functionStack.push({initialized: false, uninitialized: false}); | ||
startBlock(); | ||
@@ -78,12 +122,20 @@ } | ||
/** | ||
* Determines if there is more than one var statement in the current scope. | ||
* @returns {boolean} Returns true if it is the first var declaration, false if not. | ||
* Records whether initialized or uninitialized variables are defined in current scope. | ||
* @param {string} statementType node.kind, one of: "var", "let", or "const" | ||
* @param {ASTNode[]} declarations List of declarations | ||
* @param {Object} currentScope The scope being investigated | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function hasOnlyOneVar() { | ||
if (functionStack[functionStack.length - 1]) { | ||
return true; | ||
} else { | ||
functionStack[functionStack.length - 1] = true; | ||
return false; | ||
function recordTypes(statementType, declarations, currentScope) { | ||
for (var i = 0; i < declarations.length; i++) { | ||
if (declarations[i].init === null) { | ||
if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) { | ||
currentScope.uninitialized = true; | ||
} | ||
} else { | ||
if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) { | ||
currentScope.initialized = true; | ||
} | ||
} | ||
} | ||
@@ -93,29 +145,52 @@ } | ||
/** | ||
* Determines if there is more than one let statement in the current scope. | ||
* @returns {boolean} Returns true if it is the first let declaration, false if not. | ||
* Counts the number of initialized and uninitialized declarations in a list of declarations | ||
* @param {ASTNode[]} declarations List of declarations | ||
* @returns {Object} Counts of 'uninitialized' and 'initialized' declarations | ||
* @private | ||
*/ | ||
function hasOnlyOneLet() { | ||
if (blockStack[blockStack.length - 1].let) { | ||
return true; | ||
} else { | ||
blockStack[blockStack.length - 1].let = true; | ||
return false; | ||
function countDeclarations(declarations) { | ||
var counts = { uninitialized: 0, initialized: 0 }; | ||
for (var i = 0; i < declarations.length; i++) { | ||
if (declarations[i].init === null) { | ||
counts.uninitialized++; | ||
} else { | ||
counts.initialized++; | ||
} | ||
} | ||
return counts; | ||
} | ||
/** | ||
* Determines if there is more than one const statement in the current scope. | ||
* @returns {boolean} Returns true if it is the first const declaration, false if not. | ||
* Determines if there is more than one var statement in the current scope. | ||
* @param {string} statementType node.kind, one of: "var", "let", or "const" | ||
* @param {ASTNode[]} declarations List of declarations | ||
* @returns {boolean} Returns true if it is the first var declaration, false if not. | ||
* @private | ||
*/ | ||
function hasOnlyOneConst() { | ||
if (blockStack[blockStack.length - 1].const) { | ||
return true; | ||
} else { | ||
blockStack[blockStack.length - 1].const = true; | ||
return false; | ||
function hasOnlyOneStatement(statementType, declarations) { | ||
var currentScope; | ||
var declarationCounts = countDeclarations(declarations); | ||
if (statementType === "var") { | ||
currentScope = functionStack[functionStack.length - 1]; | ||
} else if (statementType === "let") { | ||
currentScope = blockStack[blockStack.length - 1].let; | ||
} else if (statementType === "const") { | ||
currentScope = blockStack[blockStack.length - 1].const; | ||
} | ||
if (declarationCounts.uninitialized > 0) { | ||
if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS && currentScope.uninitialized) { | ||
return false; | ||
} | ||
} | ||
if (declarationCounts.initialized > 0) { | ||
if (options[statementType] && options[statementType].initialized === MODE_ALWAYS && currentScope.initialized) { | ||
return false; | ||
} | ||
} | ||
recordTypes(statementType, declarations, currentScope); | ||
return true; | ||
} | ||
//-------------------------------------------------------------------------- | ||
@@ -135,35 +210,35 @@ // Public API | ||
"VariableDeclaration": function(node) { | ||
var declarationCount = node.declarations.length; | ||
var type = node.kind; | ||
var declarations = node.declarations; | ||
var declarationCounts = countDeclarations(declarations); | ||
if (node.kind === "var") { | ||
if (options.var === "never") { | ||
if (declarationCount > 1) { | ||
context.report(node, "Split 'var' declaration into multiple statements."); | ||
} | ||
// always | ||
if (!hasOnlyOneStatement(type, declarations)) { | ||
if (options[type] && options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { | ||
context.report(node, "Combine this with the previous '" + type + "' statement."); | ||
} else { | ||
if (hasOnlyOneVar()) { | ||
context.report(node, "Combine this with the previous 'var' statement."); | ||
if (options[type] && options[type].initialized === MODE_ALWAYS) { | ||
context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables."); | ||
} | ||
if (options[type] && options[type].uninitialized === MODE_ALWAYS) { | ||
context.report(node, "Combine this with the previous '" + type + "' statement with uninitialized variables."); | ||
} | ||
} | ||
} else if (node.kind === "let") { | ||
if (options.let === "never") { | ||
if (declarationCount > 1) { | ||
context.report(node, "Split 'let' declaration into multiple statements."); | ||
} | ||
// never | ||
if (options[type] && options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { | ||
if ((declarationCounts.uninitialized + declarationCounts.initialized) > 1) { | ||
context.report(node, "Split '" + type + "' declarations into multiple statements."); | ||
} | ||
} else { | ||
if (options[type] && options[type].initialized === MODE_NEVER) { | ||
if (declarationCounts.initialized > 1) { | ||
context.report(node, "Split initialized '" + type + "' declarations into multiple statements."); | ||
} | ||
} else { | ||
if (hasOnlyOneLet()) { | ||
context.report(node, "Combine this with the previous 'let' statement."); | ||
} | ||
} | ||
} else if (node.kind === "const") { | ||
if (options.const === "never") { | ||
if (declarationCount > 1) { | ||
context.report(node, "Split 'const' declaration into multiple statements."); | ||
if (options[type] && options[type].uninitialized === MODE_NEVER) { | ||
if (declarationCounts.uninitialized > 1) { | ||
context.report(node, "Split uninitialized '" + type + "' declarations into multiple statements."); | ||
} | ||
} else { | ||
if (hasOnlyOneConst()) { | ||
context.report(node, "Combine this with the previous 'const' statement."); | ||
} | ||
} | ||
} | ||
@@ -170,0 +245,0 @@ }, |
@@ -83,2 +83,10 @@ /** | ||
}, "'" + operator + "' should be placed at the end of the line."); | ||
} else if (style === "none") { | ||
context.report(node, { | ||
line: operatorToken.loc.end.line, | ||
column: operatorToken.loc.end.column | ||
}, "There should be no line break before or after '" + operator + "'"); | ||
} | ||
@@ -85,0 +93,0 @@ } |
@@ -29,3 +29,3 @@ /** | ||
/** | ||
* Determines whether two adjacent tokens are have whitespace between them. | ||
* Determines whether two adjacent tokens have whitespace between them. | ||
* @param {Object} left - The left token object. | ||
@@ -32,0 +32,0 @@ * @param {Object} right - The right token object. |
@@ -233,3 +233,3 @@ /** | ||
// don't do anything for namespace or default imports | ||
if (firstSpecifier.type === "ImportSpecifier" && lastSpecifier.type === "ImportSpecifier") { | ||
if (firstSpecifier && lastSpecifier && firstSpecifier.type === "ImportSpecifier" && lastSpecifier.type === "ImportSpecifier") { | ||
var first = context.getTokenBefore(firstSpecifier), | ||
@@ -246,2 +246,5 @@ second = context.getFirstToken(firstSpecifier), | ||
ExportNamedDeclaration: function(node) { | ||
if (!node.specifiers.length) { | ||
return; | ||
} | ||
@@ -248,0 +251,0 @@ var firstSpecifier = node.specifiers[0], |
@@ -24,3 +24,3 @@ /** | ||
// these both default to true, so you have to explicitly make them false | ||
requireReturn = options.requireReturn === false ? false : true, | ||
requireReturn = options.requireReturn !== false, | ||
requireParamDescription = options.requireParamDescription !== false, | ||
@@ -27,0 +27,0 @@ requireReturnDescription = options.requireReturnDescription !== false; |
{ | ||
"name": "eslint", | ||
"version": "0.20.0", | ||
"version": "0.21.0", | ||
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>", | ||
@@ -20,3 +20,3 @@ "description": "An AST-based pattern checker for JavaScript.", | ||
"perf": "node Makefile.js perf", | ||
"profile": "beefy tests/bench/bench.js --open -- -t brfs -t ./tests/bench/xform-rules.js", | ||
"profile": "beefy tests/bench/bench.js --open -- -t brfs -t ./tests/bench/xform-rules.js -r espree", | ||
"coveralls": "cat ./coverage/lcov.info | coveralls" | ||
@@ -43,3 +43,3 @@ }, | ||
"escape-string-regexp": "^1.0.2", | ||
"escope": "^3.0.0", | ||
"escope": "^3.0.1", | ||
"espree": "^2.0.1", | ||
@@ -49,2 +49,3 @@ "estraverse": "^2.0.0", | ||
"globals": "^6.1.0", | ||
"inquirer": "^0.8.2", | ||
"js-yaml": "^3.2.5", | ||
@@ -55,2 +56,3 @@ "minimatch": "^2.0.1", | ||
"optionator": "^0.5.0", | ||
"path-is-absolute": "^1.0.0", | ||
"strip-json-comments": "~1.0.1", | ||
@@ -57,0 +59,0 @@ "text-table": "~0.2.0", |
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
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
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
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
535311
188
13630
21
16
1
+ Addedinquirer@^0.8.2
+ Addedpath-is-absolute@^1.0.0
+ Addedansi-regex@1.1.1(transitive)
+ Addedcli-width@1.1.1(transitive)
+ Addedfigures@1.7.0(transitive)
+ Addedinquirer@0.8.5(transitive)
+ Addedlodash@3.10.1(transitive)
+ Addedmute-stream@0.0.4(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedreadline2@0.1.1(transitive)
+ Addedrx@2.5.3(transitive)
+ Addedstrip-ansi@2.0.1(transitive)
+ Addedthrough@2.3.8(transitive)
Updatedescope@^3.0.1