summaryrefslogtreecommitdiff
path: root/tools/eslint/lib
diff options
context:
space:
mode:
authorRich Trott <rtrott@gmail.com>2016-07-07 15:44:32 -0700
committerEvan Lucas <evanlucas@me.com>2016-07-15 08:09:42 -0500
commitdf697c486e7af5b50efca4a21105bb137cbeddbd (patch)
treef5ecf30000db4281c3538df7b9372ede231d9f8a /tools/eslint/lib
parenta81ff702cc57f9d7912cdbbcee826af9cda35915 (diff)
downloadnode-new-df697c486e7af5b50efca4a21105bb137cbeddbd.tar.gz
tools: update ESLint, fix unused vars bug
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>
Diffstat (limited to 'tools/eslint/lib')
-rw-r--r--tools/eslint/lib/ast-utils.js101
-rw-r--r--tools/eslint/lib/cli-engine.js85
-rw-r--r--tools/eslint/lib/cli.js9
-rw-r--r--tools/eslint/lib/config.js62
-rw-r--r--tools/eslint/lib/config/config-file.js21
-rw-r--r--tools/eslint/lib/config/config-initializer.js11
-rw-r--r--tools/eslint/lib/config/config-ops.js29
-rw-r--r--tools/eslint/lib/config/environments.js5
-rw-r--r--tools/eslint/lib/eslint.js59
-rw-r--r--tools/eslint/lib/file-finder.js62
-rw-r--r--tools/eslint/lib/ignored-paths.js51
-rw-r--r--tools/eslint/lib/options.js3
-rw-r--r--tools/eslint/lib/rule-context.js12
-rw-r--r--tools/eslint/lib/rules/accessor-pairs.js2
-rw-r--r--tools/eslint/lib/rules/array-bracket-spacing.js14
-rw-r--r--tools/eslint/lib/rules/arrow-body-style.js72
-rw-r--r--tools/eslint/lib/rules/arrow-parens.js30
-rw-r--r--tools/eslint/lib/rules/arrow-spacing.js8
-rw-r--r--tools/eslint/lib/rules/block-spacing.js8
-rw-r--r--tools/eslint/lib/rules/callback-return.js28
-rw-r--r--tools/eslint/lib/rules/comma-dangle.js18
-rw-r--r--tools/eslint/lib/rules/comma-spacing.js6
-rw-r--r--tools/eslint/lib/rules/comma-style.js26
-rw-r--r--tools/eslint/lib/rules/computed-property-spacing.js8
-rw-r--r--tools/eslint/lib/rules/consistent-return.js29
-rw-r--r--tools/eslint/lib/rules/curly.js11
-rw-r--r--tools/eslint/lib/rules/default-case.js6
-rw-r--r--tools/eslint/lib/rules/dot-location.js9
-rw-r--r--tools/eslint/lib/rules/eol-last.js4
-rw-r--r--tools/eslint/lib/rules/eqeqeq.js5
-rw-r--r--tools/eslint/lib/rules/func-names.js19
-rw-r--r--tools/eslint/lib/rules/generator-star-spacing.js30
-rw-r--r--tools/eslint/lib/rules/indent.js61
-rw-r--r--tools/eslint/lib/rules/key-spacing.js66
-rw-r--r--tools/eslint/lib/rules/linebreak-style.js6
-rw-r--r--tools/eslint/lib/rules/lines-around-comment.js75
-rw-r--r--tools/eslint/lib/rules/max-len.js13
-rw-r--r--tools/eslint/lib/rules/max-lines.js148
-rw-r--r--tools/eslint/lib/rules/max-statements-per-line.js153
-rw-r--r--tools/eslint/lib/rules/new-cap.js4
-rw-r--r--tools/eslint/lib/rules/new-parens.js3
-rw-r--r--tools/eslint/lib/rules/newline-after-var.js2
-rw-r--r--tools/eslint/lib/rules/newline-before-return.js25
-rw-r--r--tools/eslint/lib/rules/newline-per-chained-call.js19
-rw-r--r--tools/eslint/lib/rules/no-cond-assign.js10
-rw-r--r--tools/eslint/lib/rules/no-confusing-arrow.js3
-rw-r--r--tools/eslint/lib/rules/no-constant-condition.js33
-rw-r--r--tools/eslint/lib/rules/no-div-regex.js3
-rw-r--r--tools/eslint/lib/rules/no-duplicate-case.js3
-rw-r--r--tools/eslint/lib/rules/no-else-return.js15
-rw-r--r--tools/eslint/lib/rules/no-empty-character-class.js3
-rw-r--r--tools/eslint/lib/rules/no-empty-function.js4
-rw-r--r--tools/eslint/lib/rules/no-empty.js4
-rw-r--r--tools/eslint/lib/rules/no-extra-parens.js188
-rw-r--r--tools/eslint/lib/rules/no-extra-semi.js18
-rw-r--r--tools/eslint/lib/rules/no-implicit-coercion.js14
-rw-r--r--tools/eslint/lib/rules/no-inline-comments.js5
-rw-r--r--tools/eslint/lib/rules/no-irregular-whitespace.js88
-rw-r--r--tools/eslint/lib/rules/no-loop-func.js2
-rw-r--r--tools/eslint/lib/rules/no-mixed-operators.js212
-rw-r--r--tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js5
-rw-r--r--tools/eslint/lib/rules/no-multi-spaces.js11
-rw-r--r--tools/eslint/lib/rules/no-multiple-empty-lines.js58
-rw-r--r--tools/eslint/lib/rules/no-native-reassign.js10
-rw-r--r--tools/eslint/lib/rules/no-prototype-builtins.js52
-rw-r--r--tools/eslint/lib/rules/no-regex-spaces.js70
-rw-r--r--tools/eslint/lib/rules/no-return-assign.js64
-rw-r--r--tools/eslint/lib/rules/no-script-url.js2
-rw-r--r--tools/eslint/lib/rules/no-sequences.js14
-rw-r--r--tools/eslint/lib/rules/no-unexpected-multiline.js6
-rw-r--r--tools/eslint/lib/rules/no-unsafe-finally.js33
-rw-r--r--tools/eslint/lib/rules/no-unused-vars.js219
-rw-r--r--tools/eslint/lib/rules/no-useless-call.js18
-rw-r--r--tools/eslint/lib/rules/no-useless-computed-key.js4
-rw-r--r--tools/eslint/lib/rules/no-useless-concat.js6
-rw-r--r--tools/eslint/lib/rules/no-useless-rename.js150
-rw-r--r--tools/eslint/lib/rules/object-curly-newline.js209
-rw-r--r--tools/eslint/lib/rules/object-curly-spacing.js2
-rw-r--r--tools/eslint/lib/rules/object-property-newline.js73
-rw-r--r--tools/eslint/lib/rules/object-shorthand.js146
-rw-r--r--tools/eslint/lib/rules/one-var.js3
-rw-r--r--tools/eslint/lib/rules/operator-linebreak.js10
-rw-r--r--tools/eslint/lib/rules/padded-blocks.js20
-rw-r--r--tools/eslint/lib/rules/prefer-const.js196
-rw-r--r--tools/eslint/lib/rules/prefer-spread.js12
-rw-r--r--tools/eslint/lib/rules/require-yield.js2
-rw-r--r--tools/eslint/lib/rules/rest-spread-spacing.js107
-rw-r--r--tools/eslint/lib/rules/semi-spacing.js20
-rw-r--r--tools/eslint/lib/rules/semi.js6
-rw-r--r--tools/eslint/lib/rules/space-before-blocks.js6
-rw-r--r--tools/eslint/lib/rules/space-before-function-paren.js2
-rw-r--r--tools/eslint/lib/rules/space-infix-ops.js10
-rw-r--r--tools/eslint/lib/rules/space-unary-ops.js6
-rw-r--r--tools/eslint/lib/rules/strict.js43
-rw-r--r--tools/eslint/lib/rules/unicode-bom.js66
-rw-r--r--tools/eslint/lib/rules/valid-jsdoc.js8
-rw-r--r--tools/eslint/lib/rules/vars-on-top.js21
-rw-r--r--tools/eslint/lib/rules/wrap-iife.js6
-rw-r--r--tools/eslint/lib/rules/wrap-regex.js5
-rw-r--r--tools/eslint/lib/rules/yoda.js6
-rw-r--r--tools/eslint/lib/testers/rule-tester.js27
-rw-r--r--tools/eslint/lib/util/glob-util.js19
-rw-r--r--tools/eslint/lib/util/npm-util.js25
-rw-r--r--tools/eslint/lib/util/path-util.js7
-rw-r--r--tools/eslint/lib/util/source-code-fixer.js6
105 files changed, 3023 insertions, 791 deletions
diff --git a/tools/eslint/lib/ast-utils.js b/tools/eslint/lib/ast-utils.js
index e008beeb2a..c8d6dcb491 100644
--- a/tools/eslint/lib/ast-utils.js
+++ b/tools/eslint/lib/ast-utils.js
@@ -57,7 +57,7 @@ function isModifyingReference(reference, index, references) {
function isES5Constructor(node) {
return (
node.id &&
- node.id.name[0] === node.id.name[0].toLocaleUpperCase()
+ node.id.name[0] !== node.id.name[0].toLocaleLowerCase()
);
}
@@ -176,14 +176,14 @@ function hasJSDocThisTag(node, sourceCode) {
/**
* Determines if a node is surrounded by parentheses.
- * @param {RuleContext} context The context object passed to the rule
+ * @param {SourceCode} sourceCode The ESLint source code object
* @param {ASTNode} node The node to be checked.
* @returns {boolean} True if the node is parenthesised.
* @private
*/
-function isParenthesised(context, node) {
- var previousToken = context.getTokenBefore(node),
- nextToken = context.getTokenAfter(node);
+function isParenthesised(sourceCode, node) {
+ var previousToken = sourceCode.getTokenBefore(node),
+ nextToken = sourceCode.getTokenAfter(node);
return Boolean(previousToken && nextToken) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@@ -460,5 +460,96 @@ module.exports = {
/* istanbul ignore next */
return true;
+ },
+
+ /**
+ * Get the precedence level based on the node type
+ * @param {ASTNode} node node to evaluate
+ * @returns {int} precedence level
+ * @private
+ */
+ getPrecedence: function(node) {
+ switch (node.type) {
+ case "SequenceExpression":
+ return 0;
+
+ case "AssignmentExpression":
+ case "ArrowFunctionExpression":
+ case "YieldExpression":
+ return 1;
+
+ case "ConditionalExpression":
+ return 3;
+
+ case "LogicalExpression":
+ switch (node.operator) {
+ case "||":
+ return 4;
+ case "&&":
+ return 5;
+
+ // no default
+ }
+
+ /* falls through */
+
+ case "BinaryExpression":
+
+ switch (node.operator) {
+ case "|":
+ return 6;
+ case "^":
+ return 7;
+ case "&":
+ return 8;
+ case "==":
+ case "!=":
+ case "===":
+ case "!==":
+ return 9;
+ case "<":
+ case "<=":
+ case ">":
+ case ">=":
+ case "in":
+ case "instanceof":
+ return 10;
+ case "<<":
+ case ">>":
+ case ">>>":
+ return 11;
+ case "+":
+ case "-":
+ return 12;
+ case "*":
+ case "/":
+ case "%":
+ return 13;
+
+ // no default
+ }
+
+ /* falls through */
+
+ case "UnaryExpression":
+ return 14;
+
+ case "UpdateExpression":
+ return 15;
+
+ case "CallExpression":
+
+ // IIFE is allowed to have parens in any position (#655)
+ if (node.callee.type === "FunctionExpression") {
+ return -1;
+ }
+ return 16;
+
+ case "NewExpression":
+ return 17;
+
+ // no default
+ }
+ return 18;
}
};
diff --git a/tools/eslint/lib/cli-engine.js b/tools/eslint/lib/cli-engine.js
index 7fa1a794ea..410fd7f367 100644
--- a/tools/eslint/lib/cli-engine.js
+++ b/tools/eslint/lib/cli-engine.js
@@ -20,7 +20,6 @@ var fs = require("fs"),
lodash = require("lodash"),
debug = require("debug"),
- isAbsolute = require("path-is-absolute"),
rules = require("./rules"),
eslint = require("./eslint"),
@@ -68,6 +67,8 @@ var fs = require("fs"),
* @typedef {Object} LintResult
* @property {string} filePath The path to the file that was linted.
* @property {LintMessage[]} messages All of the messages for the result.
+ * @property {number} errorCount Number or errors for the result.
+ * @property {number} warningCount Number or warnings for the result.
*/
//------------------------------------------------------------------------------
@@ -132,23 +133,19 @@ function multipassFix(text, config, options) {
fixedResult,
fixed = false,
passNumber = 0,
- lastMessageCount,
MAX_PASSES = 10;
/**
* This loop continues until one of the following is true:
*
* 1. No more fixes have been applied.
- * 2. There are no more linting errors reported.
- * 3. The number of linting errors is no different between two passes.
- * 4. Ten passes have been made.
+ * 2. Ten passes have been made.
*
* That means anytime a fix is successfully applied, there will be another pass.
* Essentially, guaranteeing a minimum of two passes.
*/
do {
passNumber++;
- lastMessageCount = messages.length;
debug("Linting code for " + options.filename + " (pass " + passNumber + ")");
messages = eslint.verify(text, config, options);
@@ -156,6 +153,12 @@ function multipassFix(text, config, options) {
debug("Generating fixed text for " + options.filename + " (pass " + passNumber + ")");
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
+ // stop if there are any syntax errors.
+ // 'fixedResult.output' is a empty string.
+ if (messages.length === 1 && messages[0].fatal) {
+ break;
+ }
+
// keep track if any fixes were ever applied - important for return value
fixed = fixed || fixedResult.fixed;
@@ -163,8 +166,7 @@ function multipassFix(text, config, options) {
text = fixedResult.output;
} while (
- fixedResult.fixed && fixedResult.messages.length > 0 &&
- fixedResult.messages.length !== lastMessageCount &&
+ fixedResult.fixed &&
passNumber < MAX_PASSES
);
@@ -300,18 +302,34 @@ function processFile(filename, configHelper, options) {
/**
* Returns result with warning by ignore settings
- * @param {string} filePath File path of checked code
- * @returns {Result} Result with single warning
+ * @param {string} filePath - File path of checked code
+ * @param {string} baseDir - Absolute path of base directory
+ * @returns {Result} Result with single warning
* @private
*/
-function createIgnoreResult(filePath) {
+function createIgnoreResult(filePath, baseDir) {
+ var message;
+ var isHidden = /^\./.test(path.basename(filePath));
+ var isInNodeModules = baseDir && /^node_modules/.test(path.relative(baseDir, filePath));
+ var isInBowerComponents = baseDir && /^bower_components/.test(path.relative(baseDir, filePath));
+
+ if (isHidden) {
+ message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern \'!<relative/path/to/filename>\'\") to override.";
+ } else if (isInNodeModules) {
+ message = "File ignored by default. Use \"--ignore-pattern \'!node_modules/*\'\" to override.";
+ } else if (isInBowerComponents) {
+ message = "File ignored by default. Use \"--ignore-pattern \'!bower_components/*\'\" to override.";
+ } else {
+ message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
+ }
+
return {
filePath: path.resolve(filePath),
messages: [
{
fatal: false,
severity: 1,
- message: "File ignored because of a matching ignore pattern. Use --no-ignore to override."
+ message: message
}
],
errorCount: 0,
@@ -438,10 +456,6 @@ function CLIEngine(options) {
*/
this._fileCache = fileEntryCache.create(cacheFile);
- if (!this.options.cache) {
- this._fileCache.destroy();
- }
-
// load in additional rules
if (this.options.rulePaths) {
var cwd = this.options.cwd;
@@ -489,7 +503,8 @@ CLIEngine.getFormatter = function(format) {
try {
return require(formatterPath);
} catch (ex) {
- return null;
+ ex.message = "There was a problem loading formatter: " + formatterPath + "\nError: " + ex.message;
+ throw ex;
}
} else {
@@ -511,7 +526,9 @@ CLIEngine.getErrorResults = function(results) {
if (filteredMessages.length > 0) {
filtered.push({
filePath: result.filePath,
- messages: filteredMessages
+ messages: filteredMessages,
+ errorCount: filteredMessages.length,
+ warningCount: 0
});
}
});
@@ -563,7 +580,6 @@ CLIEngine.prototype = {
*/
executeOnFiles: function(patterns) {
var results = [],
- processed = {},
options = this.options,
fileCache = this._fileCache,
configHelper = new Config(options),
@@ -612,7 +628,7 @@ CLIEngine.prototype = {
var hashOfConfig;
if (warnIgnored) {
- results.push(createIgnoreResult(filename));
+ results.push(createIgnoreResult(filename, options.cwd));
return;
}
@@ -634,13 +650,6 @@ CLIEngine.prototype = {
debug("Skipping file since hasn't changed: " + filename);
/*
- * Adding the filename to the processed hashmap
- * so the reporting is not affected (showing a warning about .eslintignore being used
- * when it is not really used)
- */
- processed[filename] = true;
-
- /*
* Add the the cached results (always will be 0 error and
* 0 warnings). We should not cache results for files that
* failed, in order to guarantee that next execution will
@@ -651,12 +660,12 @@ CLIEngine.prototype = {
// move to the next file
return;
}
+ } else {
+ fileCache.destroy();
}
debug("Processing " + filename);
- processed[filename] = true;
-
var res = processFile(filename, configHelper, options);
if (options.cache) {
@@ -718,9 +727,10 @@ CLIEngine.prototype = {
* Executes the current configuration on text.
* @param {string} text A string of JavaScript code to lint.
* @param {string} filename An optional string representing the texts filename.
+ * @param {boolean} warnIgnored Always warn when a file is ignored
* @returns {Object} The results for the linting.
*/
- executeOnText: function(text, filename) {
+ executeOnText: function(text, filename, warnIgnored) {
var results = [],
stats,
@@ -729,12 +739,11 @@ CLIEngine.prototype = {
ignoredPaths = new IgnoredPaths(options);
// resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
- if (filename && !isAbsolute(filename)) {
+ if (filename && !path.isAbsolute(filename)) {
filename = path.resolve(options.cwd, filename);
}
- if (filename && (options.ignore !== false) && ignoredPaths.contains(filename)) {
-
- results.push(createIgnoreResult(filename));
+ if (filename && warnIgnored && ignoredPaths.contains(filename)) {
+ results.push(createIgnoreResult(filename, options.cwd));
} else {
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig));
}
@@ -770,12 +779,8 @@ CLIEngine.prototype = {
var ignoredPaths;
var resolvedPath = path.resolve(this.options.cwd, filePath);
- if (this.options.ignore) {
- ignoredPaths = new IgnoredPaths(this.options);
- return ignoredPaths.contains(resolvedPath);
- }
-
- return false;
+ ignoredPaths = new IgnoredPaths(this.options);
+ return ignoredPaths.contains(resolvedPath);
},
getFormatter: CLIEngine.getFormatter
diff --git a/tools/eslint/lib/cli.js b/tools/eslint/lib/cli.js
index adb70d8ce1..887c3c7671 100644
--- a/tools/eslint/lib/cli.js
+++ b/tools/eslint/lib/cli.js
@@ -74,9 +74,10 @@ function printResults(engine, results, format, outputFile) {
output,
filePath;
- formatter = engine.getFormatter(format);
- if (!formatter) {
- log.error("Could not find formatter '%s'.", format);
+ try {
+ formatter = engine.getFormatter(format);
+ } catch (e) {
+ log.error(e.message);
return false;
}
@@ -177,7 +178,7 @@ var cli = {
return 0;
}
- report = text ? engine.executeOnText(text, currentOptions.stdinFilename) : engine.executeOnFiles(files);
+ report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
if (currentOptions.fix) {
debug("Fix mode enabled - applying fixes");
CLIEngine.outputFixes(report);
diff --git a/tools/eslint/lib/config.js b/tools/eslint/lib/config.js
index b9c7061953..a485774d77 100644
--- a/tools/eslint/lib/config.js
+++ b/tools/eslint/lib/config.js
@@ -71,7 +71,7 @@ function loadConfig(configToLoad) {
/**
* Get personal config object from ~/.eslintrc.
- * @returns {Object} the personal config object (empty object if there is no personal config)
+ * @returns {Object} the personal config object (null if there is no personal config)
* @private
*/
function getPersonalConfig() {
@@ -87,7 +87,16 @@ function getPersonalConfig() {
}
}
- return config || {};
+ return config || null;
+}
+
+/**
+ * Determine if rules were explicitly passed in as options.
+ * @param {Object} options The options used to create our configuration.
+ * @returns {boolean} True if rules were passed in as options, false otherwise.
+ */
+function hasRules(options) {
+ return options.rules && Object.keys(options.rules).length > 0;
}
/**
@@ -105,7 +114,8 @@ function getLocalConfig(thisConfig, directory) {
localConfigFiles = thisConfig.findLocalConfigFiles(directory),
numFiles = localConfigFiles.length,
rootPath,
- projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd);
+ projectConfigPath = ConfigFile.getFilenameForDirectory(thisConfig.options.cwd),
+ personalConfig;
for (i = 0; i < numFiles; i++) {
@@ -140,8 +150,34 @@ function getLocalConfig(thisConfig, directory) {
config = ConfigOps.merge(localConfig, config);
}
- // Use the personal config file if there are no other local config files found.
- return found || thisConfig.useSpecificConfig ? config : ConfigOps.merge(config, getPersonalConfig());
+ if (!found && !thisConfig.useSpecificConfig) {
+
+ /*
+ * - Is there a personal config in the user's home directory? If so,
+ * merge that with the passed-in config.
+ * - Otherwise, if no rules were manually passed in, throw and error.
+ * - Note: This function is not called if useEslintrc is false.
+ */
+ personalConfig = getPersonalConfig();
+
+ if (personalConfig) {
+ config = ConfigOps.merge(config, personalConfig);
+ } else if (!hasRules(thisConfig.options)) {
+
+ // No config file, no manual configuration, and no rules, so error.
+ var noConfigError = new Error("No ESLint configuration found.");
+
+ noConfigError.messageTemplate = "no-config-found";
+ noConfigError.messageData = {
+ directory: directory,
+ filesExamined: localConfigFiles
+ };
+
+ throw noConfigError;
+ }
+ }
+
+ return config;
}
//------------------------------------------------------------------------------
@@ -231,7 +267,7 @@ Config.prototype.getConfig = function(filePath) {
}
// Step 2: Create a copy of the baseConfig
- config = ConfigOps.merge({parser: this.parser, parserOptions: this.parserOptions}, this.baseConfig);
+ config = ConfigOps.merge({}, this.baseConfig);
// Step 3: Merge in the user-specified configuration from .eslintrc and package.json
config = ConfigOps.merge(config, userConfig);
@@ -256,6 +292,20 @@ Config.prototype.getConfig = function(filePath) {
// Step 7: Merge in command line globals
config = ConfigOps.merge(config, { globals: this.globals });
+ // Only override parser if it is passed explicitly through the command line or if it's not
+ // defined yet (because the final object will at least have the parser key)
+ if (this.parser || !config.parser) {
+ config = ConfigOps.merge(config, {
+ parser: this.parser
+ });
+ }
+
+ if (this.parserOptions) {
+ config = ConfigOps.merge(config, {
+ parserOptions: this.parserOptions
+ });
+ }
+
// Step 8: Merge in command line plugins
if (this.options.plugins) {
debug("Merging command line plugins");
diff --git a/tools/eslint/lib/config/config-file.js b/tools/eslint/lib/config/config-file.js
index 51a81c7331..e2996e3eb9 100644
--- a/tools/eslint/lib/config/config-file.js
+++ b/tools/eslint/lib/config/config-file.js
@@ -20,9 +20,9 @@ var debug = require("debug"),
pathUtil = require("../util/path-util"),
ModuleResolver = require("../util/module-resolver"),
pathIsInside = require("path-is-inside"),
+ stripBom = require("strip-bom"),
stripComments = require("strip-json-comments"),
stringify = require("json-stable-stringify"),
- isAbsolutePath = require("path-is-absolute"),
defaultOptions = require("../../conf/eslint.json"),
requireUncached = require("require-uncached");
@@ -68,7 +68,7 @@ debug = debug("eslint:config-file");
* @private
*/
function readFile(filePath) {
- return fs.readFileSync(filePath, "utf8");
+ return stripBom(fs.readFileSync(filePath, "utf8"));
}
/**
@@ -80,7 +80,7 @@ function readFile(filePath) {
* @private
*/
function isFilePath(filePath) {
- return isAbsolutePath(filePath) || !/\w|@/.test(filePath.charAt(0));
+ return path.isAbsolute(filePath) || !/\w|@/.test(filePath.charAt(0));
}
/**
@@ -369,14 +369,20 @@ function applyExtends(config, filePath, relativeTo) {
* this lets us use the eslint.json file as the recommended rules
*/
parentPath = path.resolve(__dirname, "../../conf/eslint.json");
+ } else if (parentPath === "eslint:all") {
+
+ /*
+ * Add an explicit substitution for eslint:all to conf/eslint-all.js
+ */
+ parentPath = path.resolve(__dirname, "../../conf/eslint-all.js");
} else 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 = (!path.isAbsolute(parentPath) ?
+ path.join(relativeTo || path.dirname(filePath), parentPath) :
parentPath
);
}
@@ -489,7 +495,6 @@ function resolve(filePath, relativeTo) {
function load(filePath, applyEnvironments, relativeTo) {
var resolvedPath = resolve(filePath, relativeTo),
dirname = path.dirname(resolvedPath.filePath),
- basedir = getBaseDir(dirname),
lookupPath = getLookupPath(dirname),
config = loadConfigFile(resolvedPath);
@@ -508,7 +513,7 @@ function load(filePath, applyEnvironments, relativeTo) {
// include full path of parser if present
if (config.parser) {
if (isFilePath(config.parser)) {
- config.parser = path.resolve(basedir || "", config.parser);
+ config.parser = path.resolve(dirname || "", config.parser);
} else {
config.parser = resolver.resolve(config.parser, lookupPath);
}
@@ -522,7 +527,7 @@ function load(filePath, applyEnvironments, relativeTo) {
* a "parent". Load the referenced file and merge the configuration recursively.
*/
if (config.extends) {
- config = applyExtends(config, filePath, basedir);
+ config = applyExtends(config, filePath, dirname);
}
if (config.env && applyEnvironments) {
diff --git a/tools/eslint/lib/config/config-initializer.js b/tools/eslint/lib/config/config-initializer.js
index 3d0e78fefe..91d2454a8a 100644
--- a/tools/eslint/lib/config/config-initializer.js
+++ b/tools/eslint/lib/config/config-initializer.js
@@ -46,7 +46,6 @@ function writeFile(config, format) {
extname = ".json";
}
-
ConfigFile.write(config, "./.eslintrc" + extname);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd());
@@ -318,7 +317,8 @@ function promptUser(callback) {
message: "Which style guide do you want to follow?",
choices: [{name: "Google", value: "google"}, {name: "AirBnB", value: "airbnb"}, {name: "Standard", value: "standard"}],
when: function(answers) {
- return answers.source === "guide";
+ answers.packageJsonExists = npmUtil.checkPackageJson();
+ return answers.source === "guide" && answers.packageJsonExists;
}
},
{
@@ -342,13 +342,18 @@ function promptUser(callback) {
default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"],
when: function(answers) {
- return (answers.source === "guide" || answers.source === "auto");
+ return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
}
}
], function(earlyAnswers) {
// early exit if you are using a style guide
if (earlyAnswers.source === "guide") {
+ if (!earlyAnswers.packageJsonExists) {
+ log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again.");
+ return;
+ }
+
try {
config = getConfigForStyleGuide(earlyAnswers.styleguide);
writeFile(config, earlyAnswers.format);
diff --git a/tools/eslint/lib/config/config-ops.js b/tools/eslint/lib/config/config-ops.js
index 727d3afa04..d62169502b 100644
--- a/tools/eslint/lib/config/config-ops.js
+++ b/tools/eslint/lib/config/config-ops.js
@@ -23,7 +23,8 @@ var RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
map[value] = index;
return map;
- }, {});
+ }, {}),
+ VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"];
//------------------------------------------------------------------------------
// Public Interface
@@ -248,6 +249,30 @@ module.exports = {
}
return (typeof severity === "number" && severity === 2);
- }
+ },
+ /**
+ * Checks whether a given config has valid severity or not.
+ * @param {number|string|Array} ruleConfig - The configuration for an individual rule.
+ * @returns {boolean} `true` if the configuration has valid severity.
+ */
+ isValidSeverity: function(ruleConfig) {
+ var severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
+
+ if (typeof severity === "string") {
+ severity = severity.toLowerCase();
+ }
+ return VALID_SEVERITIES.indexOf(severity) !== -1;
+ },
+
+ /**
+ * Checks whether every rule of a given config has valid severity or not.
+ * @param {object} config - The configuration for rules.
+ * @returns {boolean} `true` if the configuration has valid severity.
+ */
+ isEverySeverityValid: function(config) {
+ return Object.keys(config).every(function(ruleId) {
+ return this.isValidSeverity(config[ruleId]);
+ }, this);
+ }
};
diff --git a/tools/eslint/lib/config/environments.js b/tools/eslint/lib/config/environments.js
index e7711836e5..8daef864e3 100644
--- a/tools/eslint/lib/config/environments.js
+++ b/tools/eslint/lib/config/environments.js
@@ -8,15 +8,12 @@
// Requirements
//------------------------------------------------------------------------------
-var debug = require("debug"),
- envs = require("../../conf/environments");
+var envs = require("../../conf/environments");
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
-debug = debug("eslint:enviroments");
-
var environments = Object.create(null);
/**
diff --git a/tools/eslint/lib/eslint.js b/tools/eslint/lib/eslint.js
index 3a52bb3af1..69ad96e820 100644
--- a/tools/eslint/lib/eslint.js
+++ b/tools/eslint/lib/eslint.js
@@ -9,25 +9,25 @@
// Requirements
//------------------------------------------------------------------------------
-var lodash = require("lodash"),
- Traverser = require("./util/traverser"),
+var assert = require("assert"),
+ EventEmitter = require("events").EventEmitter,
escope = require("escope"),
- Environments = require("./config/environments"),
+ levn = require("levn"),
+ lodash = require("lodash"),
blankScriptAST = require("../conf/blank-script.json"),
- rules = require("./rules"),
- RuleContext = require("./rule-context"),
- timing = require("./timing"),
- SourceCode = require("./util/source-code"),
- NodeEventGenerator = require("./util/node-event-generator"),
- CommentEventGenerator = require("./util/comment-event-generator"),
- EventEmitter = require("events").EventEmitter,
+ DEFAULT_PARSER = require("../conf/eslint.json").parser,
+ replacements = require("../conf/replacements.json"),
+ CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
ConfigOps = require("./config/config-ops"),
validator = require("./config/config-validator"),
- replacements = require("../conf/replacements.json"),
- assert = require("assert"),
- CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer");
-
-var DEFAULT_PARSER = require("../conf/eslint.json").parser;
+ Environments = require("./config/environments"),
+ CommentEventGenerator = require("./util/comment-event-generator"),
+ NodeEventGenerator = require("./util/node-event-generator"),
+ SourceCode = require("./util/source-code"),
+ Traverser = require("./util/traverser"),
+ RuleContext = require("./rule-context"),
+ rules = require("./rules"),
+ timing = require("./timing");
//------------------------------------------------------------------------------
// Helpers
@@ -80,6 +80,25 @@ function parseBooleanConfig(string, comment) {
function parseJsonConfig(string, location, messages) {
var items = {};
+ // Parses a JSON-like comment by the same way as parsing CLI option.
+ try {
+ items = levn.parse("Object", string) || {};
+
+ // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`.
+ // Also, commaless notations have invalid severity:
+ // "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"}
+ // Should ignore that case as well.
+ if (ConfigOps.isEverySeverityValid(items)) {
+ return items;
+ }
+ } catch (ex) {
+
+ // ignore to parse the string by a fallback.
+ }
+
+ // Optionator cannot parse commaless notations.
+ // But we are supporting that. So this is a fallback for that.
+ items = {};
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
try {
items = JSON.parse("{" + string + "}");
@@ -962,8 +981,14 @@ module.exports = (function() {
source: sourceCode.lines[location.line - 1] || ""
};
- // ensure there's range and text properties as well as metadata switch, otherwise it's not a valid fix
- if (fix && Array.isArray(fix.range) && (typeof fix.text === "string") && (!meta || meta.fixable)) {
+ // ensure there's range and text properties, otherwise it's not a valid fix
+ if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) {
+
+ // If rule uses fix, has metadata, but has no metadata.fixable, we should throw
+ if (meta && !meta.fixable) {
+ throw new Error("Fixable rules should export a `meta.fixable` property.");
+ }
+
problem.fix = fix;
}
diff --git a/tools/eslint/lib/file-finder.js b/tools/eslint/lib/file-finder.js
index 4dbb7544a3..4559405700 100644
--- a/tools/eslint/lib/file-finder.js
+++ b/tools/eslint/lib/file-finder.js
@@ -70,64 +70,6 @@ function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
}
/**
- * Find one instance of a specified file name in directory or in a parent directory.
- * Cache the results.
- * Does not check if a matching directory entry is a file, and intentionally
- * only searches for the first file name in this.fileNames.
- * Is currently used by lib/ignored_paths.js to find an .eslintignore file.
- * @param {string} directory The directory to start the search from.
- * @returns {string} Path of the file found, or an empty string if not found.
- */
-FileFinder.prototype.findInDirectoryOrParents = function(directory) {
- var cache = this.cache,
- child,
- dirs,
- filePath,
- i,
- names,
- searched;
-
- if (!directory) {
- directory = this.cwd;
- }
-
- if (cache.hasOwnProperty(directory)) {
- return cache[directory];
- }
-
- dirs = [];
- searched = 0;
- names = this.fileNames;
-
- (function() {
- while (directory !== child) {
- dirs[searched++] = directory;
- var filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, names);
-
- if (Object.keys(filesMap).length) {
- for (var k = 0; k < names.length; k++) {
- if (filesMap[names[k]]) {
- filePath = filesMap[names[k]];
- return;
- }
- }
- }
-
- child = directory;
-
- // Assign parent directory to directory.
- directory = path.dirname(directory);
- }
- }());
-
- for (i = 0; i < searched; i++) {
- cache[dirs[i]] = filePath;
- }
-
- return filePath || String();
-};
-
-/**
* Find all instances of files with the specified file names, in directory and
* parent directories. Cache the results.
* Does not check if a matching directory entry is a file.
@@ -146,7 +88,9 @@ FileFinder.prototype.findAllInDirectoryAndParents = function(directory) {
j,
searched;
- if (!directory) {
+ if (directory) {
+ directory = path.resolve(this.cwd, directory);
+ } else {
directory = this.cwd;
}
diff --git a/tools/eslint/lib/ignored-paths.js b/tools/eslint/lib/ignored-paths.js
index c6a710a9b5..9a7739ebee 100644
--- a/tools/eslint/lib/ignored-paths.js
+++ b/tools/eslint/lib/ignored-paths.js
@@ -24,9 +24,9 @@ debug = debug("eslint:ignored-paths");
//------------------------------------------------------------------------------
var ESLINT_IGNORE_FILENAME = ".eslintignore";
-var DEFAULT_IGNORE_PATTERNS = [
- "/node_modules/*",
- "/bower_components/*"
+var DEFAULT_IGNORE_DIRS = [
+ "node_modules/",
+ "bower_components/"
];
var DEFAULT_OPTIONS = {
dotfiles: false,
@@ -97,7 +97,9 @@ function IgnoredPaths(options) {
return ig.add(fs.readFileSync(filepath, "utf8"));
}
- this.defaultPatterns = DEFAULT_IGNORE_PATTERNS.concat(options.patterns || []);
+ this.defaultPatterns = DEFAULT_IGNORE_DIRS.map(function(dir) {
+ return "/" + dir + "*";
+ }).concat(options.patterns || []);
this.baseDir = options.cwd;
this.ig = {
@@ -124,11 +126,6 @@ function IgnoredPaths(options) {
if (options.ignore !== false) {
var ignorePath;
- if (options.ignorePattern) {
- addPattern(this.ig.custom, options.ignorePattern);
- addPattern(this.ig.default, options.ignorePattern);
- }
-
if (options.ignorePath) {
debug("Using specific ignore file");
@@ -159,6 +156,10 @@ function IgnoredPaths(options) {
addIgnoreFile(this.ig.default, ignorePath);
}
+ if (options.ignorePattern) {
+ addPattern(this.ig.custom, options.ignorePattern);
+ addPattern(this.ig.default, options.ignorePattern);
+ }
}
this.options = options;
@@ -189,4 +190,36 @@ IgnoredPaths.prototype.contains = function(filepath, category) {
};
+/**
+ * Returns a list of dir patterns for glob to ignore
+ * @returns {string[]} list of glob ignore patterns
+ */
+IgnoredPaths.prototype.getIgnoredFoldersGlobPatterns = function() {
+ var dirs = DEFAULT_IGNORE_DIRS;
+
+ if (this.options.ignore) {
+
+ /* eslint-disable no-underscore-dangle */
+
+ var patterns = this.ig.custom._rules.filter(function(rule) {
+ return rule.negative;
+ }).map(function(rule) {
+ return rule.origin;
+ });
+
+ /* eslint-enable no-underscore-dangle */
+
+ dirs = dirs.filter(function(dir) {
+ return patterns.every(function(p) {
+ return (p.indexOf("!" + dir) !== 0 && p.indexOf("!/" + dir) !== 0);
+ });
+ });
+ }
+
+
+ return dirs.map(function(dir) {
+ return dir + "**";
+ });
+};
+
module.exports = IgnoredPaths;
diff --git a/tools/eslint/lib/options.js b/tools/eslint/lib/options.js
index 7d61458581..eb58a62333 100644
--- a/tools/eslint/lib/options.js
+++ b/tools/eslint/lib/options.js
@@ -57,7 +57,6 @@ module.exports = optionator({
{
option: "parser",
type: "String",
- default: "espree",
description: "Specify the parser to be used"
},
{
@@ -115,7 +114,7 @@ module.exports = optionator({
option: "ignore",
type: "Boolean",
default: "true",
- description: "Disable use of .eslintignore"
+ description: "Disable use of ignore files and patterns"
},
{
option: "ignore-pattern",
diff --git a/tools/eslint/lib/rule-context.js b/tools/eslint/lib/rule-context.js
index 88e68abd73..49b4dfc77d 100644
--- a/tools/eslint/lib/rule-context.js
+++ b/tools/eslint/lib/rule-context.js
@@ -15,18 +15,21 @@ var RuleFixer = require("./util/rule-fixer");
//------------------------------------------------------------------------------
var PASSTHROUGHS = [
- "getAllComments",
"getAncestors",
- "getComments",
"getDeclaredVariables",
"getFilename",
+ "getScope",
+ "markVariableAsUsed",
+
+ // DEPRECATED
+ "getAllComments",
+ "getComments",
"getFirstToken",
"getFirstTokens",
"getJSDocComment",
"getLastToken",
"getLastTokens",
"getNodeByRangeIndex",
- "getScope",
"getSource",
"getSourceLines",
"getTokenAfter",
@@ -35,8 +38,7 @@ var PASSTHROUGHS = [
"getTokens",
"getTokensAfter",
"getTokensBefore",
- "getTokensBetween",
- "markVariableAsUsed"
+ "getTokensBetween"
];
//------------------------------------------------------------------------------
diff --git a/tools/eslint/lib/rules/accessor-pairs.js b/tools/eslint/lib/rules/accessor-pairs.js
index 1b91ef2715..3ed9f0dc0c 100644
--- a/tools/eslint/lib/rules/accessor-pairs.js
+++ b/tools/eslint/lib/rules/accessor-pairs.js
@@ -73,7 +73,7 @@ function isPropertyDescriptor(node) {
module.exports = {
meta: {
docs: {
- description: "Enforces getter/setter pairs in objects",
+ description: "enforce getter and setter pairs in objects",
category: "Best Practices",
recommended: false
},
diff --git a/tools/eslint/lib/rules/array-bracket-spacing.js b/tools/eslint/lib/rules/array-bracket-spacing.js
index 379ed0fa59..09598031b4 100644
--- a/tools/eslint/lib/rules/array-bracket-spacing.js
+++ b/tools/eslint/lib/rules/array-bracket-spacing.js
@@ -13,7 +13,7 @@ var astUtils = require("../ast-utils");
module.exports = {
meta: {
docs: {
- description: "Enforce spacing inside array brackets",
+ description: "enforce consistent spacing inside array brackets",
category: "Stylistic Issues",
recommended: false
},
@@ -77,7 +77,7 @@ module.exports = {
loc: token.loc.start,
message: "There should be no space after '" + token.value + "'",
fix: function(fixer) {
- var nextToken = context.getSourceCode().getTokenAfter(token);
+ var nextToken = sourceCode.getTokenAfter(token);
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
@@ -96,7 +96,7 @@ module.exports = {
loc: token.loc.start,
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
- var previousToken = context.getSourceCode().getTokenBefore(token);
+ var previousToken = sourceCode.getTokenBefore(token);
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
@@ -165,10 +165,10 @@ module.exports = {
return;
}
- var first = context.getFirstToken(node),
- second = context.getFirstToken(node, 1),
- penultimate = context.getLastToken(node, 1),
- last = context.getLastToken(node),
+ var first = sourceCode.getFirstToken(node),
+ second = sourceCode.getFirstToken(node, 1),
+ penultimate = sourceCode.getLastToken(node, 1),
+ last = sourceCode.getLastToken(node),
firstElement = node.elements[0],
lastElement = node.elements[node.elements.length - 1];
diff --git a/tools/eslint/lib/rules/arrow-body-style.js b/tools/eslint/lib/rules/arrow-body-style.js
index 79fde90f80..13486fa74b 100644
--- a/tools/eslint/lib/rules/arrow-body-style.js
+++ b/tools/eslint/lib/rules/arrow-body-style.js
@@ -16,16 +16,45 @@ module.exports = {
recommended: false
},
- schema: [
- {
- enum: ["always", "as-needed"]
- }
- ]
+ schema: {
+ anyOf: [
+ {
+ type: "array",
+ items: [
+ {
+ enum: ["always", "never"]
+ }
+ ],
+ minItems: 0,
+ maxItems: 1
+ },
+ {
+ type: "array",
+ items: [
+ {
+ enum: ["as-needed"]
+ },
+ {
+ type: "object",
+ properties: {
+ requireReturnForObjectLiteral: {type: "boolean"}
+ },
+ additionalProperties: false
+ }
+ ],
+ minItems: 0,
+ maxItems: 2
+ }
+ ]
+ }
},
create: function(context) {
- var always = context.options[0] === "always";
- var asNeeded = !context.options[0] || context.options[0] === "as-needed";
+ var options = context.options;
+ var always = options[0] === "always";
+ var asNeeded = !options[0] || options[0] === "as-needed";
+ var never = options[0] === "never";
+ var requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
/**
* Determines whether a arrow function body needs braces
@@ -36,21 +65,34 @@ module.exports = {
var arrowBody = node.body;
if (arrowBody.type === "BlockStatement") {
- var blockBody = arrowBody.body;
-
- if (blockBody.length !== 1) {
- return;
- }
-
- if (asNeeded && blockBody[0].type === "ReturnStatement") {
+ if (never) {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
+ } else {
+ var blockBody = arrowBody.body;
+
+ if (blockBody.length !== 1) {
+ return;
+ }
+
+ if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
+ blockBody[0].argument.type === "ObjectExpression") {
+ return;
+ }
+
+ if (asNeeded && blockBody[0].type === "ReturnStatement") {
+ context.report({
+ node: node,
+ loc: arrowBody.loc.start,
+ message: "Unexpected block statement surrounding arrow body."
+ });
+ }
}
} else {
- if (always) {
+ if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
context.report({
node: node,
loc: arrowBody.loc.start,
diff --git a/tools/eslint/lib/rules/arrow-parens.js b/tools/eslint/lib/rules/arrow-parens.js
index 78ce045017..86b972e800 100644
--- a/tools/eslint/lib/rules/arrow-parens.js
+++ b/tools/eslint/lib/rules/arrow-parens.js
@@ -16,6 +16,8 @@ module.exports = {
recommended: false
},
+ fixable: "code",
+
schema: [
{
enum: ["always", "as-needed"]
@@ -28,28 +30,48 @@ module.exports = {
var asNeededMessage = "Unexpected parentheses around single function argument";
var asNeeded = context.options[0] === "as-needed";
+ var sourceCode = context.getSourceCode();
+
/**
* Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function parens(node) {
- var token = context.getFirstToken(node);
+ var token = sourceCode.getFirstToken(node);
// as-needed: x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") {
- context.report(node, asNeededMessage);
+ context.report({
+ node: node,
+ message: asNeededMessage,
+ fix: function(fixer) {
+ var paramToken = context.getTokenAfter(token);
+ var closingParenToken = context.getTokenAfter(paramToken);
+
+ return fixer.replaceTextRange([
+ token.range[0],
+ closingParenToken.range[1]
+ ], paramToken.value);
+ }
+ });
}
return;
}
if (token.type === "Identifier") {
- var after = context.getTokenAfter(token);
+ var after = sourceCode.getTokenAfter(token);
// (x) => x
if (after.value !== ")") {
- context.report(node, message);
+ context.report({
+ node: node,
+ message: message,
+ fix: function(fixer) {
+ return fixer.replaceText(token, "(" + token.value + ")");
+ }
+ });
}
}
}
diff --git a/tools/eslint/lib/rules/arrow-spacing.js b/tools/eslint/lib/rules/arrow-spacing.js
index 82cec87ed2..3af5ae1f84 100644
--- a/tools/eslint/lib/rules/arrow-spacing.js
+++ b/tools/eslint/lib/rules/arrow-spacing.js
@@ -43,20 +43,22 @@ module.exports = {
rule.before = option.before !== false;
rule.after = option.after !== false;
+ var sourceCode = context.getSourceCode();
+
/**
* Get tokens of arrow(`=>`) and before/after arrow.
* @param {ASTNode} node The arrow function node.
* @returns {Object} Tokens of arrow and before/after arrow.
*/
function getTokens(node) {
- var t = context.getFirstToken(node);
+ var t = sourceCode.getFirstToken(node);
var before;
while (t.type !== "Punctuator" || t.value !== "=>") {
before = t;
- t = context.getTokenAfter(t);
+ t = sourceCode.getTokenAfter(t);
}
- var after = context.getTokenAfter(t);
+ var after = sourceCode.getTokenAfter(t);
return { before: before, arrow: t, after: after };
}
diff --git a/tools/eslint/lib/rules/block-spacing.js b/tools/eslint/lib/rules/block-spacing.js
index b6dc6e701d..54ae83d117 100644
--- a/tools/eslint/lib/rules/block-spacing.js
+++ b/tools/eslint/lib/rules/block-spacing.js
@@ -39,11 +39,11 @@ module.exports = {
function getOpenBrace(node) {
if (node.type === "SwitchStatement") {
if (node.cases.length > 0) {
- return context.getTokenBefore(node.cases[0]);
+ return sourceCode.getTokenBefore(node.cases[0]);
}
- return context.getLastToken(node, 1);
+ return sourceCode.getLastToken(node, 1);
}
- return context.getFirstToken(node);
+ return sourceCode.getFirstToken(node);
}
/**
@@ -73,7 +73,7 @@ module.exports = {
// Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node);
- var closeBrace = context.getLastToken(node);
+ var closeBrace = sourceCode.getLastToken(node);
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
diff --git a/tools/eslint/lib/rules/callback-return.js b/tools/eslint/lib/rules/callback-return.js
index a995da3a98..1d70d0c637 100644
--- a/tools/eslint/lib/rules/callback-return.js
+++ b/tools/eslint/lib/rules/callback-return.js
@@ -24,7 +24,8 @@ module.exports = {
create: function(context) {
- var callbacks = context.options[0] || ["callback", "cb", "next"];
+ var callbacks = context.options[0] || ["callback", "cb", "next"],
+ sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
@@ -47,12 +48,33 @@ module.exports = {
}
/**
+ * Check to see if a node contains only identifers
+ * @param {ASTNode} node The node to check
+ * @returns {Boolean} Whether or not the node contains only identifers
+ */
+ function containsOnlyIdentifiers(node) {
+ if (node.type === "Identifier") {
+ return true;
+ }
+
+ if (node.type === "MemberExpression") {
+ if (node.object.type === "Identifier") {
+ return true;
+ } else if (node.object.type === "MemberExpression") {
+ return containsOnlyIdentifiers(node.object);
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list.
* @returns {Boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
- return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
+ return containsOnlyIdentifiers(node.callee) && callbacks.indexOf(sourceCode.getText(node.callee)) > -1;
}
/**
@@ -90,7 +112,7 @@ module.exports = {
return {
CallExpression: function(node) {
- // if we"re not a callback we can return
+ // if we're not a callback we can return
if (!isCallback(node)) {
return;
}
diff --git a/tools/eslint/lib/rules/comma-dangle.js b/tools/eslint/lib/rules/comma-dangle.js
index 7c2451b608..d2478cacfb 100644
--- a/tools/eslint/lib/rules/comma-dangle.js
+++ b/tools/eslint/lib/rules/comma-dangle.js
@@ -31,8 +31,8 @@ module.exports = {
meta: {
docs: {
description: "require or disallow trailing commas",
- category: "Possible Errors",
- recommended: true
+ category: "Stylistic Issues",
+ recommended: false
},
fixable: "code",
@@ -143,13 +143,13 @@ module.exports = {
}
var sourceCode = context.getSourceCode(),
- trailingToken;
-
- // last item can be surrounded by parentheses for object and array literals
- if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
- trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
- } else {
+ penultimateToken = lastItem,
trailingToken = sourceCode.getTokenAfter(lastItem);
+
+ // Skip close parentheses.
+ while (trailingToken.value === ")") {
+ penultimateToken = trailingToken;
+ trailingToken = sourceCode.getTokenAfter(trailingToken);
}
if (trailingToken.value !== ",") {
@@ -158,7 +158,7 @@ module.exports = {
loc: lastItem.loc.end,
message: MISSING_MESSAGE,
fix: function(fixer) {
- return fixer.insertTextAfter(lastItem, ",");
+ return fixer.insertTextAfter(penultimateToken, ",");
}
});
}
diff --git a/tools/eslint/lib/rules/comma-spacing.js b/tools/eslint/lib/rules/comma-spacing.js
index 2a4ec1f417..22fb8b235f 100644
--- a/tools/eslint/lib/rules/comma-spacing.js
+++ b/tools/eslint/lib/rules/comma-spacing.js
@@ -136,19 +136,19 @@ module.exports = {
* @returns {void}
*/
function addNullElementsToIgnoreList(node) {
- var previousToken = context.getFirstToken(node);
+ var previousToken = sourceCode.getFirstToken(node);
node.elements.forEach(function(element) {
var token;
if (element === null) {
- token = context.getTokenAfter(previousToken);
+ token = sourceCode.getTokenAfter(previousToken);
if (isComma(token)) {
commaTokensToIgnore.push(token);
}
} else {
- token = context.getTokenAfter(element);
+ token = sourceCode.getTokenAfter(element);
}
previousToken = token;
diff --git a/tools/eslint/lib/rules/comma-style.js b/tools/eslint/lib/rules/comma-style.js
index 9c7d266d3d..173df90c33 100644
--- a/tools/eslint/lib/rules/comma-style.js
+++ b/tools/eslint/lib/rules/comma-style.js
@@ -39,9 +39,9 @@ module.exports = {
},
create: function(context) {
-
var style = context.options[0] || "last",
- exceptions = {};
+ exceptions = {},
+ sourceCode = context.getSourceCode();
if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
exceptions = context.options[1].exceptions;
@@ -115,12 +115,18 @@ module.exports = {
if (items.length > 1 || arrayLiteral) {
// seed as opening [
- previousItemToken = context.getFirstToken(node);
+ previousItemToken = sourceCode.getFirstToken(node);
items.forEach(function(item) {
- var commaToken = item ? context.getTokenBefore(item) : previousItemToken,
- currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken),
- reportItem = item || currentItemToken;
+ var commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
+ currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
+ reportItem = item || currentItemToken,
+ tokenBeforeComma = sourceCode.getTokenBefore(commaToken);
+
+ // Check if previous token is wrapped in parentheses
+ if (tokenBeforeComma && tokenBeforeComma.value === ")") {
+ previousItemToken = tokenBeforeComma;
+ }
/*
* This works by comparing three token locations:
@@ -141,7 +147,7 @@ module.exports = {
currentItemToken, reportItem);
}
- previousItemToken = item ? context.getLastToken(item) : previousItemToken;
+ previousItemToken = item ? sourceCode.getLastToken(item) : previousItemToken;
});
/*
@@ -152,12 +158,12 @@ module.exports = {
*/
if (arrayLiteral) {
- var lastToken = context.getLastToken(node),
- nextToLastToken = context.getTokenBefore(lastToken);
+ var lastToken = sourceCode.getLastToken(node),
+ nextToLastToken = sourceCode.getTokenBefore(lastToken);
if (isComma(nextToLastToken)) {
validateCommaItemSpacing(
- context.getTokenBefore(nextToLastToken),
+ sourceCode.getTokenBefore(nextToLastToken),
nextToLastToken,
lastToken,
lastToken
diff --git a/tools/eslint/lib/rules/computed-property-spacing.js b/tools/eslint/lib/rules/computed-property-spacing.js
index 1ae674e90d..89f0cc87b1 100644
--- a/tools/eslint/lib/rules/computed-property-spacing.js
+++ b/tools/eslint/lib/rules/computed-property-spacing.js
@@ -119,10 +119,10 @@ module.exports = {
var property = node[propertyName];
- var before = context.getTokenBefore(property),
- first = context.getFirstToken(property),
- last = context.getLastToken(property),
- after = context.getTokenAfter(property);
+ var before = sourceCode.getTokenBefore(property),
+ first = sourceCode.getFirstToken(property),
+ last = sourceCode.getLastToken(property),
+ after = sourceCode.getTokenAfter(property);
if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) {
diff --git a/tools/eslint/lib/rules/consistent-return.js b/tools/eslint/lib/rules/consistent-return.js
index 0e9a8c8b0f..10f1d41cf4 100644
--- a/tools/eslint/lib/rules/consistent-return.js
+++ b/tools/eslint/lib/rules/consistent-return.js
@@ -15,6 +15,16 @@ var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
/**
+ * Checks whether or not a given node is an `Identifier` node which was named a given name.
+ * @param {ASTNode} node - A node to check.
+ * @param {string} name - An expected name of the node.
+ * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
+ */
+function isIdentifier(node, name) {
+ return node.type === "Identifier" && node.name === name;
+}
+
+/**
* Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment - A CodePathSegment to check.
* @returns {boolean} `true` if the segment is unreachable.
@@ -35,10 +45,20 @@ module.exports = {
recommended: false
},
- schema: []
+ schema: [{
+ type: "object",
+ properties: {
+ treatUndefinedAsUnspecified: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }]
},
create: function(context) {
+ var options = context.options[0] || {};
+ var treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
var funcInfo = null;
/**
@@ -115,7 +135,12 @@ module.exports = {
// Reports a given return statement if it's inconsistent.
ReturnStatement: function(node) {
- var hasReturnValue = Boolean(node.argument);
+ var argument = node.argument;
+ var hasReturnValue = Boolean(argument);
+
+ if (treatUndefinedAsUnspecified && hasReturnValue) {
+ hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
+ }
if (!funcInfo.hasReturn) {
funcInfo.hasReturn = true;
diff --git a/tools/eslint/lib/rules/curly.js b/tools/eslint/lib/rules/curly.js
index 0bc5fdb3de..257366fabe 100644
--- a/tools/eslint/lib/rules/curly.js
+++ b/tools/eslint/lib/rules/curly.js
@@ -58,6 +58,8 @@ module.exports = {
var multiOrNest = (context.options[0] === "multi-or-nest");
var consistent = (context.options[1] === "consistent");
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -69,8 +71,8 @@ module.exports = {
* @private
*/
function isCollapsedOneLiner(node) {
- var before = context.getTokenBefore(node),
- last = context.getLastToken(node);
+ var before = sourceCode.getTokenBefore(node),
+ last = sourceCode.getLastToken(node);
return before.loc.start.line === last.loc.end.line;
}
@@ -82,8 +84,8 @@ module.exports = {
* @private
*/
function isOneLiner(node) {
- var first = context.getFirstToken(node),
- last = context.getLastToken(node);
+ var first = sourceCode.getFirstToken(node),
+ last = sourceCode.getLastToken(node);
return first.loc.start.line === last.loc.end.line;
}
@@ -94,7 +96,6 @@ module.exports = {
* @returns {Token} The `else` keyword token.
*/
function getElseKeyword(node) {
- var sourceCode = context.getSourceCode();
var token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") {
diff --git a/tools/eslint/lib/rules/default-case.js b/tools/eslint/lib/rules/default-case.js
index a4f3eef3cc..ae70a59284 100644
--- a/tools/eslint/lib/rules/default-case.js
+++ b/tools/eslint/lib/rules/default-case.js
@@ -13,7 +13,7 @@ var DEFAULT_COMMENT_PATTERN = /^no default$/;
module.exports = {
meta: {
docs: {
- description: "require `default` cases in <code>switch</code> statements",
+ description: "require `default` cases in `switch` statements",
category: "Best Practices",
recommended: false
},
@@ -35,6 +35,8 @@ module.exports = {
new RegExp(options.commentPattern) :
DEFAULT_COMMENT_PATTERN;
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -76,7 +78,7 @@ module.exports = {
var lastCase = last(node.cases);
- comments = context.getComments(lastCase).trailing;
+ comments = sourceCode.getComments(lastCase).trailing;
if (comments.length) {
comment = last(comments);
diff --git a/tools/eslint/lib/rules/dot-location.js b/tools/eslint/lib/rules/dot-location.js
index 0e1c257d39..2b29e0f49b 100644
--- a/tools/eslint/lib/rules/dot-location.js
+++ b/tools/eslint/lib/rules/dot-location.js
@@ -28,11 +28,12 @@ module.exports = {
create: function(context) {
- var config = context.options[0],
- onObject;
+ var config = context.options[0];
// default to onObject if no preference is passed
- onObject = config === "object" || !config;
+ var onObject = config === "object" || !config;
+
+ var sourceCode = context.getSourceCode();
/**
* Reports if the dot between object and property is on the correct loccation.
@@ -42,7 +43,7 @@ module.exports = {
* @returns {void}
*/
function checkDotLocation(obj, prop, node) {
- var dot = context.getTokenBefore(prop);
+ var dot = sourceCode.getTokenBefore(prop);
if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) {
diff --git a/tools/eslint/lib/rules/eol-last.js b/tools/eslint/lib/rules/eol-last.js
index 1bd7c2897a..60b070f1ab 100644
--- a/tools/eslint/lib/rules/eol-last.js
+++ b/tools/eslint/lib/rules/eol-last.js
@@ -35,8 +35,8 @@ module.exports = {
Program: function checkBadEOF(node) {
- // Get the whole source code, not for node only.
- var src = context.getSource(),
+ var sourceCode = context.getSourceCode(),
+ src = sourceCode.getText(),
location = {column: 1},
linebreakStyle = context.options[0] || "unix",
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";
diff --git a/tools/eslint/lib/rules/eqeqeq.js b/tools/eslint/lib/rules/eqeqeq.js
index f1d1d15429..441f5b751c 100644
--- a/tools/eslint/lib/rules/eqeqeq.js
+++ b/tools/eslint/lib/rules/eqeqeq.js
@@ -19,12 +19,13 @@ module.exports = {
schema: [
{
- enum: ["smart", "allow-null"]
+ enum: ["always", "smart", "allow-null"]
}
]
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
/**
* Checks if an expression is a typeof expression
@@ -75,7 +76,7 @@ module.exports = {
* @private
*/
function getOperatorLocation(node) {
- var opToken = context.getTokenAfter(node.left);
+ var opToken = sourceCode.getTokenAfter(node.left);
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
}
diff --git a/tools/eslint/lib/rules/func-names.js b/tools/eslint/lib/rules/func-names.js
index 51a1ffe046..44b989b2c4 100644
--- a/tools/eslint/lib/rules/func-names.js
+++ b/tools/eslint/lib/rules/func-names.js
@@ -12,15 +12,20 @@
module.exports = {
meta: {
docs: {
- description: "enforce named `function` expressions",
+ description: "require or disallow named `function` expressions",
category: "Stylistic Issues",
recommended: false
},
- schema: []
+ schema: [
+ {
+ enum: ["always", "never"]
+ }
+ ]
},
create: function(context) {
+ var never = context.options[0] === "never";
/**
* Determines whether the current FunctionExpression node is a get, set, or
@@ -44,8 +49,14 @@ module.exports = {
var name = node.id && node.id.name;
- if (!name && !isObjectOrClassMethod()) {
- context.report(node, "Missing function expression name.");
+ if (never) {
+ if (name) {
+ context.report(node, "Unexpected function expression name.");
+ }
+ } else {
+ if (!name && !isObjectOrClassMethod()) {
+ context.report(node, "Missing function expression name.");
+ }
}
}
};
diff --git a/tools/eslint/lib/rules/generator-star-spacing.js b/tools/eslint/lib/rules/generator-star-spacing.js
index f05f9f4201..0cab2be50e 100644
--- a/tools/eslint/lib/rules/generator-star-spacing.js
+++ b/tools/eslint/lib/rules/generator-star-spacing.js
@@ -52,6 +52,26 @@ module.exports = {
return option;
}(context.options[0]));
+ var sourceCode = context.getSourceCode();
+
+ /**
+ * Gets `*` token from a given node.
+ *
+ * @param {ASTNode} node - A node to get `*` token. This is one of
+ * FunctionDeclaration, FunctionExpression, Property, and
+ * MethodDefinition.
+ * @returns {Token} `*` token.
+ */
+ function getStarToken(node) {
+ var token = sourceCode.getFirstToken(node);
+
+ while (token.value !== "*") {
+ token = sourceCode.getTokenAfter(token);
+ }
+
+ return token;
+ }
+
/**
* Checks the spacing between two tokens before or after the star token.
* @param {string} side Either "before" or "after".
@@ -98,18 +118,18 @@ module.exports = {
}
if (node.parent.method || node.parent.type === "MethodDefinition") {
- starToken = context.getTokenBefore(node, 1);
+ starToken = getStarToken(node.parent);
} else {
- starToken = context.getFirstToken(node, 1);
+ starToken = getStarToken(node);
}
- // Only check before when preceded by `function` keyword
- prevToken = context.getTokenBefore(starToken);
+ // Only check before when preceded by `function`|`static` keyword
+ prevToken = sourceCode.getTokenBefore(starToken);
if (prevToken.value === "function" || prevToken.value === "static") {
checkSpacing("before", prevToken, starToken);
}
- nextToken = context.getTokenAfter(starToken);
+ nextToken = sourceCode.getTokenAfter(starToken);
checkSpacing("after", starToken, nextToken);
}
diff --git a/tools/eslint/lib/rules/indent.js b/tools/eslint/lib/rules/indent.js
index 3e8f4b92e1..3c0c9827d8 100644
--- a/tools/eslint/lib/rules/indent.js
+++ b/tools/eslint/lib/rules/indent.js
@@ -67,6 +67,10 @@ module.exports = {
}
}
]
+ },
+ outerIIFEBody: {
+ type: "integer",
+ minimum: 0
}
},
additionalProperties: false
@@ -87,9 +91,12 @@ module.exports = {
var: DEFAULT_VARIABLE_INDENT,
let: DEFAULT_VARIABLE_INDENT,
const: DEFAULT_VARIABLE_INDENT
- }
+ },
+ outerIIFEBody: null
};
+ var sourceCode = context.getSourceCode();
+
if (context.options.length) {
if (context.options[0] === "tab") {
indentSize = 1;
@@ -114,6 +121,10 @@ module.exports = {
} else if (typeof variableDeclaratorRules === "object") {
lodash.assign(options.VariableDeclarator, variableDeclaratorRules);
}
+
+ if (typeof opts.outerIIFEBody === "number") {
+ options.outerIIFEBody = opts.outerIIFEBody;
+ }
}
}
@@ -206,15 +217,15 @@ module.exports = {
}
/**
- * Get node indent
+ * Get the actual indent of node
* @param {ASTNode|Token} node Node to examine
* @param {boolean} [byLastLine=false] get indent of node's last line
* @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {int} Indent
*/
function getNodeIndent(node, byLastLine, excludeCommas) {
- var token = byLastLine ? context.getLastToken(node) : context.getFirstToken(node);
- var src = context.getSource(token, token.loc.start.column);
+ var token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
+ var src = sourceCode.getText(token, token.loc.start.column);
var regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal;
var indent = regExp.exec(src);
@@ -228,7 +239,7 @@ module.exports = {
* @returns {boolean} true if its the first in the its start line
*/
function isNodeFirstInLine(node, byEndLocation) {
- var firstToken = byEndLocation === true ? context.getLastToken(node, 1) : context.getTokenBefore(node),
+ var firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node),
startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line,
endLine = firstToken ? firstToken.loc.end.line : -1;
@@ -263,7 +274,7 @@ module.exports = {
function checkNodesIndent(nodes, indent, excludeCommas) {
nodes.forEach(function(node) {
if (node.type === "IfStatement" && node.alternate) {
- var elseToken = context.getTokenBefore(node.alternate);
+ var elseToken = sourceCode.getTokenBefore(node.alternate);
checkNodeIndent(elseToken, indent, excludeCommas);
}
@@ -278,7 +289,7 @@ module.exports = {
* @returns {void}
*/
function checkLastNodeLineIndent(node, lastLineIndent) {
- var lastToken = context.getLastToken(node);
+ var lastToken = sourceCode.getLastToken(node);
var endIndent = getNodeIndent(lastToken, true);
if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) {
@@ -356,9 +367,25 @@ module.exports = {
return false;
}
+ /**
+ * Check to see if the node is a file level IIFE
+ * @param {ASTNode} node The function node to check.
+ * @returns {boolean} True if the node is the outer IIFE
+ */
+ function isOuterIIFE(node) {
+ var parent = node.parent;
+
+ return (
+ parent.type === "CallExpression" &&
+ parent.callee === node &&
+ parent.parent.type === "ExpressionStatement" &&
+ parent.parent.parent && parent.parent.parent.type === "Program"
+ );
+ }
+
/**
* Check indent for function block content
- * @param {ASTNode} node node to examine
+ * @param {ASTNode} node A BlockStatement node that is inside of a function.
* @returns {void}
*/
function checkIndentInFunctionBlock(node) {
@@ -407,8 +434,14 @@ module.exports = {
}
}
- // function body indent should be indent + indent size
- indent += indentSize;
+ // function body indent should be indent + indent size, unless this
+ // is the outer IIFE and that option is enabled.
+ var functionOffset = indentSize;
+
+ if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
+ functionOffset = options.outerIIFEBody * indentSize;
+ }
+ indent += functionOffset;
// check if the node is inside a variable
var parentVarNode = getVariableDeclaratorNode(node);
@@ -421,7 +454,7 @@ module.exports = {
checkNodesIndent(node.body, indent);
}
- checkLastNodeLineIndent(node, indent - indentSize);
+ checkLastNodeLineIndent(node, indent - functionOffset);
}
@@ -431,7 +464,7 @@ module.exports = {
* @returns {boolean} Whether or not the block starts and ends on the same line.
*/
function isSingleLineNode(node) {
- var lastToken = context.getLastToken(node),
+ var lastToken = sourceCode.getLastToken(node),
startLine = node.loc.start.line,
endLine = lastToken.loc.end.line;
@@ -641,11 +674,11 @@ module.exports = {
checkNodesIndent(elements, elementsIndent, true);
// Only check the last line if there is any token after the last item
- if (context.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
+ if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
return;
}
- var tokenBeforeLastElement = context.getTokenBefore(lastElement);
+ var tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement);
if (tokenBeforeLastElement.value === ",") {
diff --git a/tools/eslint/lib/rules/key-spacing.js b/tools/eslint/lib/rules/key-spacing.js
index 1bc14aefec..1cf677865d 100644
--- a/tools/eslint/lib/rules/key-spacing.js
+++ b/tools/eslint/lib/rules/key-spacing.js
@@ -118,6 +118,8 @@ module.exports = {
recommended: false
},
+ fixable: "whitespace",
+
schema: [{
anyOf: [
{
@@ -196,6 +198,8 @@ module.exports = {
multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options));
+ var sourceCode = context.getSourceCode();
+
/**
* Determines if the given property is key-value property.
* @param {ASTNode} property Property node to check.
@@ -220,7 +224,7 @@ module.exports = {
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
- node = context.getTokenAfter(node);
+ node = sourceCode.getTokenAfter(node);
}
return prevNode;
@@ -235,7 +239,7 @@ module.exports = {
function getNextColon(node) {
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
- node = context.getTokenAfter(node);
+ node = sourceCode.getTokenAfter(node);
}
return node;
@@ -250,7 +254,7 @@ module.exports = {
var key = property.key;
if (property.computed) {
- return context.getSource().slice(key.range[0], key.range[1]);
+ return sourceCode.getText().slice(key.range[0], key.range[1]);
}
return property.key.name || property.key.value;
@@ -268,9 +272,16 @@ module.exports = {
*/
function report(property, side, whitespace, expected, mode) {
var diff = whitespace.length - expected,
- key = property.key,
- firstTokenAfterColon = context.getTokenAfter(getNextColon(key)),
- location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start;
+ nextColon = getNextColon(property.key),
+ tokenBeforeColon = sourceCode.getTokenBefore(nextColon),
+ tokenAfterColon = sourceCode.getTokenAfter(nextColon),
+ isKeySide = side === "key",
+ locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
+ isExtra = diff > 0,
+ diffAbs = Math.abs(diff),
+ spaces = Array(diffAbs + 1).join(" "),
+ fix,
+ range;
if ((
diff && mode === "strict" ||
@@ -278,10 +289,41 @@ module.exports = {
diff > 0 && !expected && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace))
) {
- context.report(property[side], location, messages[side], {
- error: diff > 0 ? "Extra" : "Missing",
- computed: property.computed ? "computed " : "",
- key: getKey(property)
+ if (isExtra) {
+
+ // Remove whitespace
+ if (isKeySide) {
+ range = [tokenBeforeColon.end, tokenBeforeColon.end + diffAbs];
+ } else {
+ range = [tokenAfterColon.start - diffAbs, tokenAfterColon.start];
+ }
+ fix = function(fixer) {
+ return fixer.removeRange(range);
+ };
+ } else {
+
+ // Add whitespace
+ if (isKeySide) {
+ fix = function(fixer) {
+ return fixer.insertTextAfter(tokenBeforeColon, spaces);
+ };
+ } else {
+ fix = function(fixer) {
+ return fixer.insertTextBefore(tokenAfterColon, spaces);
+ };
+ }
+ }
+
+ context.report({
+ node: property[side],
+ loc: locStart,
+ message: messages[side],
+ data: {
+ error: isExtra ? "Extra" : "Missing",
+ computed: property.computed ? "computed " : "",
+ key: getKey(property)
+ },
+ fix: fix
});
}
}
@@ -295,7 +337,7 @@ module.exports = {
function getKeyWidth(property) {
var startToken, endToken;
- startToken = context.getFirstToken(property);
+ startToken = sourceCode.getFirstToken(property);
endToken = getLastTokenBeforeColon(property.key);
return endToken.range[1] - startToken.range[0];
@@ -307,7 +349,7 @@ module.exports = {
* @returns {Object} Whitespace before and after the property's colon.
*/
function getPropertyWhitespace(property) {
- var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice(
+ var whitespace = /(\s*):(\s*)/.exec(sourceCode.getText().slice(
property.key.range[1], property.value.range[0]
));
diff --git a/tools/eslint/lib/rules/linebreak-style.js b/tools/eslint/lib/rules/linebreak-style.js
index d99d7e5857..5e6b819f3f 100644
--- a/tools/eslint/lib/rules/linebreak-style.js
+++ b/tools/eslint/lib/rules/linebreak-style.js
@@ -31,6 +31,8 @@ module.exports = {
var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -57,7 +59,7 @@ module.exports = {
var linebreakStyle = context.options[0] || "unix",
expectedLF = linebreakStyle === "unix",
expectedLFChars = expectedLF ? "\n" : "\r\n",
- source = context.getSource(),
+ source = sourceCode.getText(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g,
match,
index,
@@ -77,7 +79,7 @@ module.exports = {
node: node,
loc: {
line: i,
- column: context.getSourceLines()[i - 1].length
+ column: sourceCode.lines[i - 1].length
},
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG,
fix: createFix(range, expectedLFChars)
diff --git a/tools/eslint/lib/rules/lines-around-comment.js b/tools/eslint/lib/rules/lines-around-comment.js
index 92fc3252ae..a227fe4184 100644
--- a/tools/eslint/lib/rules/lines-around-comment.js
+++ b/tools/eslint/lib/rules/lines-around-comment.js
@@ -8,7 +8,8 @@
// Requirements
//------------------------------------------------------------------------------
-var lodash = require("lodash");
+var lodash = require("lodash"),
+ astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Helpers
@@ -51,16 +52,6 @@ function getCommentLineNums(comments) {
return lines;
}
-/**
- * Determines if a value is an array.
- * @param {number} val The value we wish to check for in the array..
- * @param {Array} array An array.
- * @returns {boolean} True if the value is in the array..
- */
-function contains(val, array) {
- return array.indexOf(val) > -1;
-}
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -73,6 +64,8 @@ module.exports = {
recommended: false
},
+ fixable: "whitespace",
+
schema: [
{
type: "object",
@@ -126,6 +119,22 @@ module.exports = {
var sourceCode = context.getSourceCode();
+ var lines = sourceCode.lines,
+ numLines = lines.length + 1,
+ comments = sourceCode.getAllComments(),
+ commentLines = getCommentLineNums(comments),
+ emptyLines = getEmptyLineNums(lines),
+ commentAndEmptyLines = commentLines.concat(emptyLines);
+
+ /**
+ * 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 whether or not comments are on lines starting with or ending with code
* @param {ASTNode} node The comment node to check.
@@ -137,18 +146,18 @@ module.exports = {
token = node;
do {
token = sourceCode.getTokenOrCommentBefore(token);
- } while (token && (token.type === "Block" || token.type === "Line"));
+ } while (isCommentNodeType(token));
- if (token && token.loc.end.line === node.loc.start.line) {
+ if (token && astUtils.isTokenOnSameLine(token, node)) {
return true;
}
token = node;
do {
token = sourceCode.getTokenOrCommentAfter(token);
- } while (token && (token.type === "Block" || token.type === "Line"));
+ } while (isCommentNodeType(token));
- if (token && token.loc.start.line === node.loc.end.line) {
+ if (token && astUtils.isTokenOnSameLine(node, token)) {
return true;
}
@@ -267,14 +276,6 @@ module.exports = {
* @returns {void}
*/
function checkForEmptyLine(node, opts) {
-
- var lines = context.getSourceLines(),
- numLines = lines.length + 1,
- comments = context.getAllComments(),
- commentLines = getCommentLineNums(comments),
- emptyLines = getEmptyLineNums(lines),
- commentAndEmptyLines = commentLines.concat(emptyLines);
-
var after = opts.after,
before = opts.before;
@@ -305,14 +306,34 @@ module.exports = {
return;
}
+ var previousTokenOrComment = sourceCode.getTokenOrCommentBefore(node);
+ var nextTokenOrComment = sourceCode.getTokenOrCommentAfter(node);
+
// check for newline before
- if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
- context.report(node, "Expected line before comment.");
+ if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
+ !(isCommentNodeType(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, node))) {
+ var lineStart = node.range[0] - node.loc.start.column;
+ var range = [lineStart, lineStart];
+
+ context.report({
+ node: node,
+ message: "Expected line before comment.",
+ fix: function(fixer) {
+ return fixer.insertTextBeforeRange(range, "\n");
+ }
+ });
}
// check for newline after
- if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
- context.report(node, "Expected line after comment.");
+ if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
+ !(isCommentNodeType(nextTokenOrComment) && astUtils.isTokenOnSameLine(node, nextTokenOrComment))) {
+ context.report({
+ node: node,
+ message: "Expected line after comment.",
+ fix: function(fixer) {
+ return fixer.insertTextAfter(node, "\n");
+ }
+ });
}
}
diff --git a/tools/eslint/lib/rules/max-len.js b/tools/eslint/lib/rules/max-len.js
index 1ba539a119..b5813bbfaa 100644
--- a/tools/eslint/lib/rules/max-len.js
+++ b/tools/eslint/lib/rules/max-len.js
@@ -81,6 +81,8 @@ module.exports = {
*/
var URL_REGEXP = /[^:/?#]:\/\/[^?#]/;
+ var sourceCode = context.getSourceCode();
+
/**
* Computes the length of a line that may contain tabs. The width of each
* tab will be the number of spaces to the next tab stop.
@@ -155,11 +157,12 @@ module.exports = {
*/
function isFullLineComment(line, lineNumber, comment) {
var start = comment.loc.start,
- end = comment.loc.end;
+ end = comment.loc.end,
+ isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim();
return comment &&
- (start.line < lineNumber || (start.line === lineNumber && start.column === 0)) &&
- (end.line > lineNumber || end.column === line.length);
+ (start.line < lineNumber || (start.line === lineNumber && isFirstTokenOnLine)) &&
+ (end.line > lineNumber || (end.line === lineNumber && end.column === line.length));
}
/**
@@ -185,10 +188,10 @@ module.exports = {
function checkProgramForMaxLength(node) {
// split (honors line-ending)
- var lines = context.getSourceLines(),
+ var lines = sourceCode.lines,
// list of comments to ignore
- comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [],
+ comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? sourceCode.getAllComments() : [],
// we iterate over comments in parallel with the lines
commentsIndex = 0;
diff --git a/tools/eslint/lib/rules/max-lines.js b/tools/eslint/lib/rules/max-lines.js
new file mode 100644
index 0000000000..751310e81d
--- /dev/null
+++ b/tools/eslint/lib/rules/max-lines.js
@@ -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"
+ });
+ }
+ }
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/max-statements-per-line.js b/tools/eslint/lib/rules/max-statements-per-line.js
index 64058f0d30..55f09746c3 100644
--- a/tools/eslint/lib/rules/max-statements-per-line.js
+++ b/tools/eslint/lib/rules/max-statements-per-line.js
@@ -21,7 +21,8 @@ module.exports = {
type: "object",
properties: {
max: {
- type: "integer"
+ type: "integer",
+ minimum: 0
}
},
additionalProperties: false
@@ -31,72 +32,81 @@ module.exports = {
create: function(context) {
- var options = context.options[0] || {},
+ var sourceCode = context.getSourceCode(),
+ options = context.options[0] || {},
lastStatementLine = 0,
numberOfStatementsOnThisLine = 0,
- maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
+ maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1,
+ message = "This line has too many statements. Maximum allowed is " + maxStatementsPerLine + ".";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
+ var SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/;
+
/**
- * Reports a node
- * @param {ASTNode} node The node to report
- * @returns {void}
- * @private
+ * Gets the actual last token of a given node.
+ *
+ * @param {ASTNode} node - A node to get. This is a node except EmptyStatement.
+ * @returns {Token} The actual last token.
*/
- function report(node) {
- context.report(
- node,
- "This line has too many statements. Maximum allowed is {{max}}.",
- { max: maxStatementsPerLine });
+ function getActualLastToken(node) {
+ var lastToken = sourceCode.getLastToken(node);
+
+ if (lastToken.value === ";") {
+ lastToken = sourceCode.getTokenBefore(lastToken);
+ }
+ return lastToken;
}
/**
- * Enforce a maximum number of statements per line
- * @param {ASTNode} nodes Array of nodes to evaluate
+ * Addresses a given node.
+ * It updates the state of this rule, then reports the node if the node violated this rule.
+ *
+ * @param {ASTNode} node - A node to check.
* @returns {void}
- * @private
*/
- function enforceMaxStatementsPerLine(nodes) {
- if (nodes.length < 1) {
+ function enterStatement(node) {
+ var line = node.loc.start.line;
+
+ // Skip to allow non-block statements if this is direct child of control statements.
+ // `if (a) foo();` is counted as 1.
+ // But `if (a) foo(); else foo();` should be counted as 2.
+ if (SINGLE_CHILD_ALLOWED.test(node.parent.type) &&
+ node.parent.alternate !== node
+ ) {
return;
}
- for (var i = 0, l = nodes.length; i < l; ++i) {
- var currentStatement = nodes[i];
+ // Update state.
+ if (line === lastStatementLine) {
+ numberOfStatementsOnThisLine += 1;
+ } else {
+ numberOfStatementsOnThisLine = 1;
+ lastStatementLine = line;
+ }
- if (currentStatement.loc.start.line === lastStatementLine) {
- ++numberOfStatementsOnThisLine;
- } else {
- numberOfStatementsOnThisLine = 1;
- lastStatementLine = currentStatement.loc.end.line;
- }
- if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
- report(currentStatement);
- }
+ // Reports if the node violated this rule.
+ if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
+ context.report({node: node, message: message});
}
}
/**
- * Check each line in the body of a node
- * @param {ASTNode} node node to evaluate
+ * Updates the state of this rule with the end line of leaving node to check with the next statement.
+ *
+ * @param {ASTNode} node - A node to check.
* @returns {void}
- * @private
*/
- function checkLinesInBody(node) {
- enforceMaxStatementsPerLine(node.body);
- }
+ function leaveStatement(node) {
+ var line = getActualLastToken(node).loc.end.line;
- /**
- * Check each line in the consequent of a switch case
- * @param {ASTNode} node node to evaluate
- * @returns {void}
- * @private
- */
- function checkLinesInConsequent(node) {
- enforceMaxStatementsPerLine(node.consequent);
+ // Update state.
+ if (line !== lastStatementLine) {
+ numberOfStatementsOnThisLine = 1;
+ lastStatementLine = line;
+ }
}
//--------------------------------------------------------------------------
@@ -104,10 +114,61 @@ module.exports = {
//--------------------------------------------------------------------------
return {
- Program: checkLinesInBody,
- BlockStatement: checkLinesInBody,
- SwitchCase: checkLinesInConsequent
- };
+ BreakStatement: enterStatement,
+ ClassDeclaration: enterStatement,
+ ContinueStatement: enterStatement,
+ DebuggerStatement: enterStatement,
+ DoWhileStatement: enterStatement,
+ ExpressionStatement: enterStatement,
+ ForInStatement: enterStatement,
+ ForOfStatement: enterStatement,
+ ForStatement: enterStatement,
+ FunctionDeclaration: enterStatement,
+ IfStatement: enterStatement,
+ ImportDeclaration: enterStatement,
+ LabeledStatement: enterStatement,
+ ReturnStatement: enterStatement,
+ SwitchStatement: enterStatement,
+ ThrowStatement: enterStatement,
+ TryStatement: enterStatement,
+ VariableDeclaration: enterStatement,
+ WhileStatement: enterStatement,
+ WithStatement: enterStatement,
+ ExportNamedDeclaration: enterStatement,
+ ExportDefaultDeclaration: enterStatement,
+ ExportAllDeclaration: enterStatement,
+ "BreakStatement:exit": leaveStatement,
+ "ClassDeclaration:exit": leaveStatement,
+ "ContinueStatement:exit": leaveStatement,
+ "DebuggerStatement:exit": leaveStatement,
+ "DoWhileStatement:exit": leaveStatement,
+ "ExpressionStatement:exit": leaveStatement,
+ "ForInStatement:exit": leaveStatement,
+ "ForOfStatement:exit": leaveStatement,
+ "ForStatement:exit": leaveStatement,
+ "FunctionDeclaration:exit": leaveStatement,
+ "IfStatement:exit": leaveStatement,
+ "ImportDeclaration:exit": leaveStatement,
+ "LabeledStatement:exit": leaveStatement,
+ "ReturnStatement:exit": leaveStatement,
+ "SwitchStatement:exit": leaveStatement,
+ "ThrowStatement:exit": leaveStatement,
+ "TryStatement:exit": leaveStatement,
+ "VariableDeclaration:exit": leaveStatement,
+ "WhileStatement:exit": leaveStatement,
+ "WithStatement:exit": leaveStatement,
+ "ExportNamedDeclaration:exit": leaveStatement,
+ "ExportDefaultDeclaration:exit": leaveStatement,
+ "ExportAllDeclaration:exit": leaveStatement,
+
+ // For backward compatibility.
+ // Empty blocks should be warned if `{max: 0}` was given.
+ BlockStatement: function reportIfZero(node) {
+ if (maxStatementsPerLine === 0 && node.body.length === 0) {
+ context.report({node: node, message: message});
+ }
+ }
+ };
}
};
diff --git a/tools/eslint/lib/rules/new-cap.js b/tools/eslint/lib/rules/new-cap.js
index 697b60d0f1..2dabb30a65 100644
--- a/tools/eslint/lib/rules/new-cap.js
+++ b/tools/eslint/lib/rules/new-cap.js
@@ -127,6 +127,8 @@ module.exports = {
var listeners = {};
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -186,7 +188,7 @@ module.exports = {
* @returns {Boolean} Returns true if the callee may be capitalized
*/
function isCapAllowed(allowedMap, node, calleeName) {
- if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) {
+ if (allowedMap[calleeName] || allowedMap[sourceCode.getText(node.callee)]) {
return true;
}
diff --git a/tools/eslint/lib/rules/new-parens.js b/tools/eslint/lib/rules/new-parens.js
index 0d7a84e3cc..ec6106647a 100644
--- a/tools/eslint/lib/rules/new-parens.js
+++ b/tools/eslint/lib/rules/new-parens.js
@@ -21,11 +21,12 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
return {
NewExpression: function(node) {
- var tokens = context.getTokens(node);
+ var tokens = sourceCode.getTokens(node);
var prenticesTokens = tokens.filter(function(token) {
return token.value === "(" || token.value === ")";
});
diff --git a/tools/eslint/lib/rules/newline-after-var.js b/tools/eslint/lib/rules/newline-after-var.js
index fd80c8c542..8801407c0b 100644
--- a/tools/eslint/lib/rules/newline-after-var.js
+++ b/tools/eslint/lib/rules/newline-after-var.js
@@ -35,7 +35,7 @@ module.exports = {
var mode = context.options[0] === "never" ? "never" : "always";
// Cache starting and ending line numbers of comments for faster lookup
- var commentEndLine = context.getAllComments().reduce(function(result, token) {
+ var commentEndLine = sourceCode.getAllComments().reduce(function(result, token) {
result[token.loc.start.line] = token.loc.end.line;
return result;
}, {});
diff --git a/tools/eslint/lib/rules/newline-before-return.js b/tools/eslint/lib/rules/newline-before-return.js
index 77f3aedaa8..5c8a139358 100644
--- a/tools/eslint/lib/rules/newline-before-return.js
+++ b/tools/eslint/lib/rules/newline-before-return.js
@@ -133,32 +133,17 @@ module.exports = {
return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
}
- /**
- * Reports expected/unexpected newline before return statement
- * @param {ASTNode} node - the node to report in the event of an error
- * @param {boolean} isExpected - whether the newline is expected or not
- * @returns {void}
- * @private
- */
- function reportError(node, isExpected) {
- var expected = isExpected ? "Expected" : "Unexpected";
-
- context.report({
- node: node,
- message: expected + " newline before return statement."
- });
- }
-
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
ReturnStatement: function(node) {
- if (isFirstNode(node) && hasNewlineBefore(node)) {
- reportError(node, false);
- } else if (!isFirstNode(node) && !hasNewlineBefore(node)) {
- reportError(node, true);
+ if (!isFirstNode(node) && !hasNewlineBefore(node)) {
+ context.report({
+ node: node,
+ message: "Expected newline before return statement."
+ });
}
}
};
diff --git a/tools/eslint/lib/rules/newline-per-chained-call.js b/tools/eslint/lib/rules/newline-per-chained-call.js
index 80401415aa..c412d53e99 100644
--- a/tools/eslint/lib/rules/newline-per-chained-call.js
+++ b/tools/eslint/lib/rules/newline-per-chained-call.js
@@ -36,6 +36,23 @@ module.exports = {
var options = context.options[0] || {},
ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
+ var sourceCode = context.getSourceCode();
+
+ /**
+ * Gets the property text of a given MemberExpression node.
+ * If the text is multiline, this returns only the first line.
+ *
+ * @param {ASTNode} node - A MemberExpression node to get.
+ * @returns {string} The property text of the node.
+ */
+ function getPropertyText(node) {
+ var prefix = node.computed ? "[" : ".";
+ var lines = sourceCode.getText(node.property).split(/\r\n|\r|\n/g);
+ var suffix = node.computed && lines.length === 1 ? "]" : "";
+
+ return prefix + lines[0] + suffix;
+ }
+
return {
"CallExpression:exit": function(node) {
if (!node.callee || node.callee.type !== "MemberExpression") {
@@ -55,7 +72,7 @@ module.exports = {
context.report(
callee.property,
callee.property.loc.start,
- "Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`."
+ "Expected line break before `" + getPropertyText(callee) + "`."
);
}
}
diff --git a/tools/eslint/lib/rules/no-cond-assign.js b/tools/eslint/lib/rules/no-cond-assign.js
index 27b99c6b54..e0979ddaf2 100644
--- a/tools/eslint/lib/rules/no-cond-assign.js
+++ b/tools/eslint/lib/rules/no-cond-assign.js
@@ -34,6 +34,8 @@ module.exports = {
var prohibitAssign = (context.options[0] || "except-parens");
+ var sourceCode = context.getSourceCode();
+
/**
* Check whether an AST node is the test expression for a conditional statement.
* @param {!Object} node The node to test.
@@ -68,8 +70,8 @@ module.exports = {
* @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
*/
function isParenthesised(node) {
- var previousToken = context.getTokenBefore(node),
- nextToken = context.getTokenAfter(node);
+ var previousToken = sourceCode.getTokenBefore(node),
+ nextToken = sourceCode.getTokenAfter(node);
return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
@@ -81,8 +83,8 @@ module.exports = {
* @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
*/
function isParenthesisedTwice(node) {
- var previousToken = context.getTokenBefore(node, 1),
- nextToken = context.getTokenAfter(node, 1);
+ var previousToken = sourceCode.getTokenBefore(node, 1),
+ nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
diff --git a/tools/eslint/lib/rules/no-confusing-arrow.js b/tools/eslint/lib/rules/no-confusing-arrow.js
index d951a53cf9..1f18aa3567 100644
--- a/tools/eslint/lib/rules/no-confusing-arrow.js
+++ b/tools/eslint/lib/rules/no-confusing-arrow.js
@@ -44,6 +44,7 @@ module.exports = {
create: function(context) {
var config = context.options[0] || {};
+ var sourceCode = context.getSourceCode();
/**
* Reports if an arrow function contains an ambiguous conditional.
@@ -53,7 +54,7 @@ module.exports = {
function checkArrowFunc(node) {
var body = node.body;
- if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) {
+ if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) {
context.report(node, "Arrow function used ambiguously with a conditional expression.");
}
}
diff --git a/tools/eslint/lib/rules/no-constant-condition.js b/tools/eslint/lib/rules/no-constant-condition.js
index 0072491bee..7c4ede7f78 100644
--- a/tools/eslint/lib/rules/no-constant-condition.js
+++ b/tools/eslint/lib/rules/no-constant-condition.js
@@ -17,10 +17,23 @@ module.exports = {
recommended: true
},
- schema: []
+ schema: [
+ {
+ type: "object",
+ properties: {
+ checkLoops: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }
+
+ ]
},
create: function(context) {
+ var options = context.options[0] || {},
+ checkLoops = options.checkLoops !== false;
//--------------------------------------------------------------------------
// Helpers
@@ -102,6 +115,18 @@ module.exports = {
}
}
+ /**
+ * Checks node when checkLoops option is enabled
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkLoop(node) {
+ if (checkLoops) {
+ checkConstantCondition(node);
+ }
+ }
+
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -109,9 +134,9 @@ module.exports = {
return {
ConditionalExpression: checkConstantCondition,
IfStatement: checkConstantCondition,
- WhileStatement: checkConstantCondition,
- DoWhileStatement: checkConstantCondition,
- ForStatement: checkConstantCondition
+ WhileStatement: checkLoop,
+ DoWhileStatement: checkLoop,
+ ForStatement: checkLoop
};
}
diff --git a/tools/eslint/lib/rules/no-div-regex.js b/tools/eslint/lib/rules/no-div-regex.js
index 58c44662cb..75a6085595 100644
--- a/tools/eslint/lib/rules/no-div-regex.js
+++ b/tools/eslint/lib/rules/no-div-regex.js
@@ -21,11 +21,12 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
return {
Literal: function(node) {
- var token = context.getFirstToken(node);
+ var token = sourceCode.getFirstToken(node);
if (token.type === "RegularExpression" && token.value[1] === "=") {
context.report(node, "A regular expression literal can be confused with '/='.");
diff --git a/tools/eslint/lib/rules/no-duplicate-case.js b/tools/eslint/lib/rules/no-duplicate-case.js
index 1308730a63..8c877ed4e8 100644
--- a/tools/eslint/lib/rules/no-duplicate-case.js
+++ b/tools/eslint/lib/rules/no-duplicate-case.js
@@ -22,13 +22,14 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
return {
SwitchStatement: function(node) {
var mapping = {};
node.cases.forEach(function(switchCase) {
- var key = context.getSource(switchCase.test);
+ var key = sourceCode.getText(switchCase.test);
if (mapping[key]) {
context.report(switchCase, "Duplicate case label.");
diff --git a/tools/eslint/lib/rules/no-else-return.js b/tools/eslint/lib/rules/no-else-return.js
index 4678d320af..528d4ca566 100644
--- a/tools/eslint/lib/rules/no-else-return.js
+++ b/tools/eslint/lib/rules/no-else-return.js
@@ -33,7 +33,7 @@ module.exports = {
* @returns {void}
*/
function displayReport(node) {
- context.report(node, "Unexpected 'else' after 'return'.");
+ context.report(node, "Unnecessary 'else' after 'return'.");
}
/**
@@ -112,14 +112,13 @@ module.exports = {
// If we have a BlockStatement, check each consequent body node.
return node.body.some(checkForReturnOrIf);
- } else {
-
- /*
- * If not a block statement, make sure the consequent isn't a
- * ReturnStatement or an IfStatement with returns on both paths.
- */
- return checkForReturnOrIf(node);
}
+
+ /*
+ * If not a block statement, make sure the consequent isn't a
+ * ReturnStatement or an IfStatement with returns on both paths.
+ */
+ return checkForReturnOrIf(node);
}
//--------------------------------------------------------------------------
diff --git a/tools/eslint/lib/rules/no-empty-character-class.js b/tools/eslint/lib/rules/no-empty-character-class.js
index e015e0cc06..34ef78a396 100644
--- a/tools/eslint/lib/rules/no-empty-character-class.js
+++ b/tools/eslint/lib/rules/no-empty-character-class.js
@@ -39,11 +39,12 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
return {
Literal: function(node) {
- var token = context.getFirstToken(node);
+ var token = sourceCode.getFirstToken(node);
if (token.type === "RegularExpression" && !regex.test(token.value)) {
context.report(node, "Empty class.");
diff --git a/tools/eslint/lib/rules/no-empty-function.js b/tools/eslint/lib/rules/no-empty-function.js
index 412614b501..0102acff51 100644
--- a/tools/eslint/lib/rules/no-empty-function.js
+++ b/tools/eslint/lib/rules/no-empty-function.js
@@ -121,6 +121,8 @@ module.exports = {
var options = context.options[0] || {};
var allowed = options.allow || [];
+ var sourceCode = context.getSourceCode();
+
/**
* Reports a given function node if the node matches the following patterns.
*
@@ -139,7 +141,7 @@ module.exports = {
if (allowed.indexOf(kind) === -1 &&
node.body.type === "BlockStatement" &&
node.body.body.length === 0 &&
- context.getComments(node.body).trailing.length === 0
+ sourceCode.getComments(node.body).trailing.length === 0
) {
context.report({
node: node,
diff --git a/tools/eslint/lib/rules/no-empty.js b/tools/eslint/lib/rules/no-empty.js
index 8f32428304..1302a90753 100644
--- a/tools/eslint/lib/rules/no-empty.js
+++ b/tools/eslint/lib/rules/no-empty.js
@@ -35,6 +35,8 @@ module.exports = {
var options = context.options[0] || {},
allowEmptyCatch = options.allowEmptyCatch || false;
+ var sourceCode = context.getSourceCode();
+
return {
BlockStatement: function(node) {
@@ -53,7 +55,7 @@ module.exports = {
}
// any other block is only allowed to be empty, if it contains a comment
- if (context.getComments(node).trailing.length > 0) {
+ if (sourceCode.getComments(node).trailing.length > 0) {
return;
}
diff --git a/tools/eslint/lib/rules/no-extra-parens.js b/tools/eslint/lib/rules/no-extra-parens.js
index cf129394ba..c33a64920f 100644
--- a/tools/eslint/lib/rules/no-extra-parens.js
+++ b/tools/eslint/lib/rules/no-extra-parens.js
@@ -40,7 +40,8 @@ module.exports = {
type: "object",
properties: {
conditionalAssign: {type: "boolean"},
- nestedBinaryExpressions: {type: "boolean"}
+ nestedBinaryExpressions: {type: "boolean"},
+ returnAssign: {type: "boolean"}
},
additionalProperties: false
}
@@ -53,11 +54,14 @@ module.exports = {
},
create: function(context) {
- var isParenthesised = astUtils.isParenthesised.bind(astUtils, context);
+ var sourceCode = context.getSourceCode();
+
+ var isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
+ var precedence = astUtils.getPrecedence;
var ALL_NODES = context.options[0] !== "functions";
var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
var NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
- var sourceCode = context.getSourceCode();
+ var EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
/**
* Determines if this rule should be enforced for a node given the current configuration.
@@ -76,8 +80,8 @@ module.exports = {
* @private
*/
function isParenthesisedTwice(node) {
- var previousToken = context.getTokenBefore(node, 1),
- nextToken = context.getTokenAfter(node, 1);
+ var previousToken = sourceCode.getTokenBefore(node, 1),
+ nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@@ -116,6 +120,64 @@ module.exports = {
}
/**
+ * Determines if a node is in a return statement
+ * @param {ASTNode} node - The node to be checked.
+ * @returns {boolean} True if the node is in a return statement.
+ * @private
+ */
+ function isInReturnStatement(node) {
+ while (node) {
+ if (node.type === "ReturnStatement" ||
+ (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement")) {
+ return true;
+ }
+ node = node.parent;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if a node is or contains an assignment expression
+ * @param {ASTNode} node - The node to be checked.
+ * @returns {boolean} True if the node is or contains an assignment expression.
+ * @private
+ */
+ function containsAssignment(node) {
+ if (node.type === "AssignmentExpression") {
+ return true;
+ } else if (node.type === "ConditionalExpression" &&
+ (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
+ return true;
+ } else if ((node.left && node.left.type === "AssignmentExpression") ||
+ (node.right && node.right.type === "AssignmentExpression")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
+ * @param {ASTNode} node - The node to be checked.
+ * @returns {boolean} True if the assignment can be parenthesised.
+ * @private
+ */
+ function isReturnAssignException(node) {
+ if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
+ return false;
+ }
+
+ if (node.type === "ReturnStatement") {
+ return node.argument && containsAssignment(node.argument);
+ } else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
+ return containsAssignment(node.body);
+ } else {
+ return containsAssignment(node);
+ }
+ }
+
+ /**
* Determines if a node following a [no LineTerminator here] restriction is
* surrounded by (potentially) invalid extra parentheses.
* @param {Token} token - The token preceding the [no LineTerminator here] restriction.
@@ -195,105 +257,13 @@ module.exports = {
}
/**
- * Get the precedence level based on the node type
- * @param {ASTNode} node node to evaluate
- * @returns {int} precedence level
- * @private
- */
- function precedence(node) {
-
- switch (node.type) {
- case "SequenceExpression":
- return 0;
-
- case "AssignmentExpression":
- case "ArrowFunctionExpression":
- case "YieldExpression":
- return 1;
-
- case "ConditionalExpression":
- return 3;
-
- case "LogicalExpression":
- switch (node.operator) {
- case "||":
- return 4;
- case "&&":
- return 5;
-
- // no default
- }
-
- /* falls through */
-
- case "BinaryExpression":
-
- switch (node.operator) {
- case "|":
- return 6;
- case "^":
- return 7;
- case "&":
- return 8;
- case "==":
- case "!=":
- case "===":
- case "!==":
- return 9;
- case "<":
- case "<=":
- case ">":
- case ">=":
- case "in":
- case "instanceof":
- return 10;
- case "<<":
- case ">>":
- case ">>>":
- return 11;
- case "+":
- case "-":
- return 12;
- case "*":
- case "/":
- case "%":
- return 13;
-
- // no default
- }
-
- /* falls through */
-
- case "UnaryExpression":
- return 14;
-
- case "UpdateExpression":
- return 15;
-
- case "CallExpression":
-
- // IIFE is allowed to have parens in any position (#655)
- if (node.callee.type === "FunctionExpression") {
- return -1;
- }
- return 16;
-
- case "NewExpression":
- return 17;
-
- // no default
- }
- return 18;
- }
-
- /**
* Report the node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function report(node) {
- var previousToken = context.getTokenBefore(node);
+ var previousToken = sourceCode.getTokenBefore(node);
context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
}
@@ -368,6 +338,10 @@ module.exports = {
},
ArrowFunctionExpression: function(node) {
+ if (isReturnAssignException(node)) {
+ return;
+ }
+
if (node.body.type !== "BlockStatement") {
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
report(node.body);
@@ -383,6 +357,10 @@ module.exports = {
},
AssignmentExpression: function(node) {
+ if (isReturnAssignException(node)) {
+ return;
+ }
+
if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
report(node.right);
}
@@ -392,12 +370,18 @@ module.exports = {
CallExpression: dryCallNew,
ConditionalExpression: function(node) {
+ if (isReturnAssignException(node)) {
+ return;
+ }
+
if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
report(node.test);
}
+
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
report(node.consequent);
}
+
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
report(node.alternate);
}
@@ -413,7 +397,7 @@ module.exports = {
var firstToken, secondToken, firstTokens;
if (hasExcessParens(node.expression)) {
- firstTokens = context.getFirstTokens(node.expression, 2);
+ firstTokens = sourceCode.getFirstTokens(node.expression, 2);
firstToken = firstTokens[0];
secondToken = firstTokens[1];
@@ -476,7 +460,7 @@ module.exports = {
!(
(node.object.type === "Literal" &&
typeof node.object.value === "number" &&
- /^[0-9]+$/.test(context.getFirstToken(node.object).value))
+ /^[0-9]+$/.test(sourceCode.getFirstToken(node.object).value))
||
// RegExp literal is allowed to have parens (#1589)
@@ -511,6 +495,10 @@ module.exports = {
ReturnStatement: function(node) {
var returnToken = sourceCode.getFirstToken(node);
+ if (isReturnAssignException(node)) {
+ return;
+ }
+
if (node.argument &&
hasExcessParensNoLineTerminator(returnToken, node.argument) &&
diff --git a/tools/eslint/lib/rules/no-extra-semi.js b/tools/eslint/lib/rules/no-extra-semi.js
index e451b9e42e..679a16641b 100644
--- a/tools/eslint/lib/rules/no-extra-semi.js
+++ b/tools/eslint/lib/rules/no-extra-semi.js
@@ -22,6 +22,7 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
/**
* Reports an unnecessary semicolon error.
@@ -48,7 +49,7 @@ module.exports = {
function checkForPartOfClassBody(firstToken) {
for (var token = firstToken;
token.type === "Punctuator" && token.value !== "}";
- token = context.getTokenAfter(token)
+ token = sourceCode.getTokenAfter(token)
) {
if (token.value === ";") {
report(token);
@@ -65,7 +66,16 @@ module.exports = {
*/
EmptyStatement: function(node) {
var parent = node.parent,
- allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"];
+ allowedParentTypes = [
+ "ForStatement",
+ "ForInStatement",
+ "ForOfStatement",
+ "WhileStatement",
+ "DoWhileStatement",
+ "IfStatement",
+ "LabeledStatement",
+ "WithStatement"
+ ];
if (allowedParentTypes.indexOf(parent.type) === -1) {
report(node);
@@ -78,7 +88,7 @@ module.exports = {
* @returns {void}
*/
ClassBody: function(node) {
- checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`.
+ checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`.
},
/**
@@ -87,7 +97,7 @@ module.exports = {
* @returns {void}
*/
MethodDefinition: function(node) {
- checkForPartOfClassBody(context.getTokenAfter(node));
+ checkForPartOfClassBody(sourceCode.getTokenAfter(node));
}
};
diff --git a/tools/eslint/lib/rules/no-implicit-coercion.js b/tools/eslint/lib/rules/no-implicit-coercion.js
index 058d9f3572..113c205855 100644
--- a/tools/eslint/lib/rules/no-implicit-coercion.js
+++ b/tools/eslint/lib/rules/no-implicit-coercion.js
@@ -179,6 +179,8 @@ module.exports = {
var options = parseOptions(context.options[0]),
operatorAllowed = false;
+ var sourceCode = context.getSourceCode();
+
return {
UnaryExpression: function(node) {
@@ -188,7 +190,7 @@ module.exports = {
context.report(
node,
"use `Boolean({{code}})` instead.", {
- code: context.getSource(node.argument.argument)
+ code: sourceCode.getText(node.argument.argument)
});
}
@@ -198,7 +200,7 @@ module.exports = {
context.report(
node,
"use `{{code}} !== -1` instead.", {
- code: context.getSource(node.argument)
+ code: sourceCode.getText(node.argument)
});
}
@@ -208,7 +210,7 @@ module.exports = {
context.report(
node,
"use `Number({{code}})` instead.", {
- code: context.getSource(node.argument)
+ code: sourceCode.getText(node.argument)
});
}
},
@@ -224,7 +226,7 @@ module.exports = {
context.report(
node,
"use `Number({{code}})` instead.", {
- code: context.getSource(nonNumericOperand)
+ code: sourceCode.getText(nonNumericOperand)
});
}
@@ -234,7 +236,7 @@ module.exports = {
context.report(
node,
"use `String({{code}})` instead.", {
- code: context.getSource(getOtherOperand(node, ""))
+ code: sourceCode.getText(getOtherOperand(node, ""))
});
}
},
@@ -247,7 +249,7 @@ module.exports = {
context.report(
node,
"use `{{code}} = String({{code}})` instead.", {
- code: context.getSource(getOtherOperand(node, ""))
+ code: sourceCode.getText(getOtherOperand(node, ""))
});
}
}
diff --git a/tools/eslint/lib/rules/no-inline-comments.js b/tools/eslint/lib/rules/no-inline-comments.js
index 7835ed30fc..e313eac06f 100644
--- a/tools/eslint/lib/rules/no-inline-comments.js
+++ b/tools/eslint/lib/rules/no-inline-comments.js
@@ -22,6 +22,7 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
/**
* Will check that comments are not on lines starting with or ending with code
@@ -32,8 +33,8 @@ module.exports = {
function testCodeAroundComment(node) {
// Get the whole line and cut it off at the start of the comment
- var startLine = String(context.getSourceLines()[node.loc.start.line - 1]);
- var endLine = String(context.getSourceLines()[node.loc.end.line - 1]);
+ var startLine = String(sourceCode.lines[node.loc.start.line - 1]);
+ var endLine = String(sourceCode.lines[node.loc.end.line - 1]);
var preamble = startLine.slice(0, node.loc.start.column).trim();
diff --git a/tools/eslint/lib/rules/no-irregular-whitespace.js b/tools/eslint/lib/rules/no-irregular-whitespace.js
index 1dbea8f5a9..032dd96c11 100644
--- a/tools/eslint/lib/rules/no-irregular-whitespace.js
+++ b/tools/eslint/lib/rules/no-irregular-whitespace.js
@@ -7,6 +7,15 @@
"use strict";
//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+var ALL_IRREGULARS = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
+var IRREGULAR_WHITESPACE = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
+var IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
+var LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g;
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -24,6 +33,15 @@ module.exports = {
properties: {
skipComments: {
type: "boolean"
+ },
+ skipStrings: {
+ type: "boolean"
+ },
+ skipTemplates: {
+ type: "boolean"
+ },
+ skipRegExps: {
+ type: "boolean"
}
},
additionalProperties: false
@@ -33,9 +51,6 @@ module.exports = {
create: function(context) {
- 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;
-
// Module store of errors that we have found
var errors = [];
@@ -45,6 +60,11 @@ module.exports = {
// Lookup the `skipComments` option, which defaults to `false`.
var options = context.options[0] || {};
var skipComments = !!options.skipComments;
+ var skipStrings = options.skipStrings !== false;
+ var skipRegExps = !!options.skipRegExps;
+ var skipTemplates = !!options.skipTemplates;
+
+ var sourceCode = context.getSourceCode();
/**
* Removes errors that occur inside a string node
@@ -75,10 +95,27 @@ module.exports = {
* @private
*/
function removeInvalidNodeErrorsInIdentifierOrLiteral(node) {
- if (typeof node.value === "string") {
+ var shouldCheckStrings = skipStrings && (typeof node.value === "string");
+ var shouldCheckRegExps = skipRegExps && (node.value instanceof RegExp);
+
+ if (shouldCheckStrings || shouldCheckRegExps) {
// If we have irregular characters remove them from the errors list
- if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) {
+ if (ALL_IRREGULARS.test(node.raw)) {
+ removeWhitespaceError(node);
+ }
+ }
+ }
+
+ /**
+ * Checks template string literal 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.
+ * @returns {void}
+ * @private
+ */
+ function removeInvalidNodeErrorsInTemplateLiteral(node) {
+ if (typeof node.value.raw === "string") {
+ if (ALL_IRREGULARS.test(node.value.raw)) {
removeWhitespaceError(node);
}
}
@@ -91,7 +128,7 @@ module.exports = {
* @private
*/
function removeInvalidNodeErrorsInComment(node) {
- if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) {
+ if (ALL_IRREGULARS.test(node.value)) {
removeWhitespaceError(node);
}
}
@@ -103,14 +140,14 @@ module.exports = {
* @private
*/
function checkForIrregularWhitespace(node) {
- var sourceLines = context.getSourceLines();
+ var sourceLines = sourceCode.lines;
sourceLines.forEach(function(sourceLine, lineIndex) {
var lineNumber = lineIndex + 1,
location,
match;
- while ((match = irregularWhitespace.exec(sourceLine)) !== null) {
+ while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
location = {
line: lineNumber,
column: match.index
@@ -128,15 +165,15 @@ module.exports = {
* @private
*/
function checkForIrregularLineTerminators(node) {
- var source = context.getSource(),
- sourceLines = context.getSourceLines(),
- linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g),
+ var source = sourceCode.getText(),
+ sourceLines = sourceCode.lines,
+ linebreaks = source.match(LINE_BREAK),
lastLineIndex = -1,
lineIndex,
location,
match;
- while ((match = irregularLineTerminators.exec(source)) !== null) {
+ while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
location = {
@@ -166,8 +203,10 @@ module.exports = {
*/
function noop() {}
- return {
- Program: function(node) {
+ var nodes = {};
+
+ if (ALL_IRREGULARS.test(sourceCode.getText())) {
+ nodes.Program = function(node) {
/*
* As we can easily fire warnings for all white space issues with
@@ -182,13 +221,14 @@ module.exports = {
checkForIrregularWhitespace(node);
checkForIrregularLineTerminators(node);
- },
+ };
- Identifier: removeInvalidNodeErrorsInIdentifierOrLiteral,
- Literal: removeInvalidNodeErrorsInIdentifierOrLiteral,
- LineComment: skipComments ? rememberCommentNode : noop,
- BlockComment: skipComments ? rememberCommentNode : noop,
- "Program:exit": function() {
+ nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
+ nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
+ nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
+ nodes.LineComment = skipComments ? rememberCommentNode : noop;
+ nodes.BlockComment = skipComments ? rememberCommentNode : noop;
+ nodes["Program:exit"] = function() {
if (skipComments) {
@@ -200,7 +240,11 @@ module.exports = {
errors.forEach(function(error) {
context.report.apply(context, error);
});
- }
- };
+ };
+ } else {
+ nodes.Program = noop;
+ }
+
+ return nodes;
}
};
diff --git a/tools/eslint/lib/rules/no-loop-func.js b/tools/eslint/lib/rules/no-loop-func.js
index 2b76093cce..247dc52cd4 100644
--- a/tools/eslint/lib/rules/no-loop-func.js
+++ b/tools/eslint/lib/rules/no-loop-func.js
@@ -73,7 +73,7 @@ function getContainingLoopNode(node) {
* @returns {ASTNode} The most outer loop node.
*/
function getTopLoopNode(node, excludedNode) {
- var retv = null;
+ var retv = node;
var border = excludedNode ? excludedNode.range[1] : 0;
while (node && node.range[0] >= border) {
diff --git a/tools/eslint/lib/rules/no-mixed-operators.js b/tools/eslint/lib/rules/no-mixed-operators.js
new file mode 100644
index 0000000000..9a8b1c3925
--- /dev/null
+++ b/tools/eslint/lib/rules/no-mixed-operators.js
@@ -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
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js
index b3cdd38a60..74553f6511 100644
--- a/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js
+++ b/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js
@@ -24,6 +24,7 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
var smartTabs,
ignoredLocs = [];
@@ -86,8 +87,8 @@ module.exports = {
*/
var regex = /^(?=[\t ]*(\t | \t))/,
match,
- lines = context.getSourceLines(),
- comments = context.getAllComments();
+ lines = sourceCode.lines,
+ comments = sourceCode.getAllComments();
comments.forEach(function(comment) {
ignoredLocs.push(comment.loc);
diff --git a/tools/eslint/lib/rules/no-multi-spaces.js b/tools/eslint/lib/rules/no-multi-spaces.js
index d6c0e4198d..2fd89ef4dc 100644
--- a/tools/eslint/lib/rules/no-multi-spaces.js
+++ b/tools/eslint/lib/rules/no-multi-spaces.js
@@ -95,8 +95,9 @@ module.exports = {
return {
Program: function() {
- var source = context.getSource(),
- allComments = context.getAllComments(),
+ var sourceCode = context.getSourceCode(),
+ source = sourceCode.getText(),
+ allComments = sourceCode.getAllComments(),
pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g, // note: repeating space
token,
previousToken,
@@ -121,12 +122,12 @@ module.exports = {
// do not flag anything inside of comments
if (!isIndexInComment(pattern.lastIndex, allComments)) {
- token = context.getTokenByRangeStart(pattern.lastIndex);
+ token = sourceCode.getTokenByRangeStart(pattern.lastIndex);
if (token) {
- previousToken = context.getTokenBefore(token);
+ previousToken = sourceCode.getTokenBefore(token);
if (hasExceptions) {
- parent = context.getNodeByRangeIndex(pattern.lastIndex - 1);
+ parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
}
if (!parent || !exceptions[parent.type]) {
diff --git a/tools/eslint/lib/rules/no-multiple-empty-lines.js b/tools/eslint/lib/rules/no-multiple-empty-lines.js
index 02a41fdbe3..7508164d38 100644
--- a/tools/eslint/lib/rules/no-multiple-empty-lines.js
+++ b/tools/eslint/lib/rules/no-multiple-empty-lines.js
@@ -17,6 +17,8 @@ module.exports = {
recommended: false
},
+ fixable: "whitespace",
+
schema: [
{
type: "object",
@@ -52,10 +54,12 @@ module.exports = {
if (context.options.length) {
max = context.options[0].max;
- maxEOF = context.options[0].maxEOF;
- maxBOF = context.options[0].maxBOF;
+ maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
+ maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
}
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -73,22 +77,35 @@ module.exports = {
},
"Program:exit": function checkBlankLines(node) {
- var lines = context.getSourceLines(),
- currentLocation = -1,
- lastLocation,
+ var lines = sourceCode.lines,
+ fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [],
+ firstNonBlankLine = -1,
+ trimmedLines = [],
+ linesRangeStart = [],
blankCounter = 0,
+ currentLocation,
+ lastLocation,
location,
firstOfEndingBlankLines,
- firstNonBlankLine = -1,
- trimmedLines = [];
+ diff,
+ fix,
+ rangeStart,
+ rangeEnd;
+ fix = function(fixer) {
+ return fixer.removeRange([rangeStart, rangeEnd]);
+ };
+
+ linesRangeStart.push(0);
lines.forEach(function(str, i) {
- var trimmed = str.trim();
+ var length = i < fullLines.length ? fullLines[i].length : 0,
+ trimmed = str.trim();
if ((firstNonBlankLine === -1) && (trimmed !== "")) {
firstNonBlankLine = i;
}
+ linesRangeStart.push(linesRangeStart[linesRangeStart.length - 1] + length);
trimmedLines.push(trimmed);
});
@@ -120,9 +137,17 @@ module.exports = {
// Aggregate and count blank lines
if (firstNonBlankLine > maxBOF) {
- context.report(node, 0,
- "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed.");
+ diff = firstNonBlankLine - maxBOF;
+ rangeStart = linesRangeStart[firstNonBlankLine - diff];
+ rangeEnd = linesRangeStart[firstNonBlankLine];
+ context.report({
+ node: node,
+ loc: node.loc.start,
+ message: "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed.",
+ fix: fix
+ });
}
+ currentLocation = firstNonBlankLine - 1;
lastLocation = currentLocation;
currentLocation = trimmedLines.indexOf("", currentLocation + 1);
@@ -141,20 +166,29 @@ module.exports = {
// within the file, not at the end
if (blankCounter >= max) {
+ diff = blankCounter - max + 1;
+ rangeStart = linesRangeStart[location.line - diff];
+ rangeEnd = linesRangeStart[location.line];
+
context.report({
node: node,
loc: location,
- message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed."
+ message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed.",
+ fix: fix
});
}
} else {
// inside the last blank lines
if (blankCounter > maxEOF) {
+ diff = blankCounter - maxEOF + 1;
+ rangeStart = linesRangeStart[location.line - diff];
+ rangeEnd = linesRangeStart[location.line - 1];
context.report({
node: node,
loc: location,
- message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed."
+ message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed.",
+ fix: fix
});
}
}
diff --git a/tools/eslint/lib/rules/no-native-reassign.js b/tools/eslint/lib/rules/no-native-reassign.js
index 7d45e277d5..8b75f022a0 100644
--- a/tools/eslint/lib/rules/no-native-reassign.js
+++ b/tools/eslint/lib/rules/no-native-reassign.js
@@ -1,5 +1,5 @@
/**
- * @fileoverview Rule to flag when re-assigning native objects
+ * @fileoverview Rule to disallow assignments to native objects or read-only global variables
* @author Ilya Volodin
*/
@@ -12,9 +12,9 @@
module.exports = {
meta: {
docs: {
- description: "disallow reassigning native objects",
+ description: "disallow assignments to native objects or read-only global variables",
category: "Best Practices",
- recommended: false
+ recommended: true
},
schema: [
@@ -55,14 +55,14 @@ module.exports = {
) {
context.report({
node: identifier,
- message: "{{name}} is a read-only native object.",
+ message: "Read-only global '{{name}}' should not be modified.",
data: identifier
});
}
}
/**
- * Reports write references if a given variable is readonly builtin.
+ * Reports write references if a given variable is read-only builtin.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
diff --git a/tools/eslint/lib/rules/no-prototype-builtins.js b/tools/eslint/lib/rules/no-prototype-builtins.js
new file mode 100644
index 0000000000..febb1459be
--- /dev/null
+++ b/tools/eslint/lib/rules/no-prototype-builtins.js
@@ -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
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/no-regex-spaces.js b/tools/eslint/lib/rules/no-regex-spaces.js
index 0584131924..a2f1d48f71 100644
--- a/tools/eslint/lib/rules/no-regex-spaces.js
+++ b/tools/eslint/lib/rules/no-regex-spaces.js
@@ -12,7 +12,7 @@
module.exports = {
meta: {
docs: {
- description: "disallow multiple spaces in regular expression literals",
+ description: "disallow multiple spaces in regular expressions",
category: "Possible Errors",
recommended: true
},
@@ -21,24 +21,66 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
- return {
+ /**
+ * Validate regular expressions
+ * @param {ASTNode} node node to validate
+ * @param {string} value regular expression to validate
+ * @returns {void}
+ * @private
+ */
+ function checkRegex(node, value) {
+ var multipleSpacesRegex = /( {2,})+?/,
+ regexResults = multipleSpacesRegex.exec(value);
- Literal: function(node) {
- var token = context.getFirstToken(node),
- nodeType = token.type,
- nodeValue = token.value,
- multipleSpacesRegex = /( {2,})+?/,
- regexResults;
+ if (regexResults !== null) {
+ context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}.");
+ }
+ }
- if (nodeType === "RegularExpression") {
- regexResults = multipleSpacesRegex.exec(nodeValue);
+ /**
+ * Validate regular expression literals
+ * @param {ASTNode} node node to validate
+ * @returns {void}
+ * @private
+ */
+ function checkLiteral(node) {
+ var token = sourceCode.getFirstToken(node),
+ nodeType = token.type,
+ nodeValue = token.value;
- if (regexResults !== null) {
- context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}.");
- }
- }
+ if (nodeType === "RegularExpression") {
+ checkRegex(node, nodeValue);
}
+ }
+
+ /**
+ * Check if node is a string
+ * @param {ASTNode} node node to evaluate
+ * @returns {boolean} True if its a string
+ * @private
+ */
+ function isString(node) {
+ return node && node.type === "Literal" && typeof node.value === "string";
+ }
+
+ /**
+ * Validate strings passed to the RegExp constructor
+ * @param {ASTNode} node node to validate
+ * @returns {void}
+ * @private
+ */
+ function checkFunction(node) {
+ if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) {
+ checkRegex(node, node.arguments[0].value);
+ }
+ }
+
+ return {
+ Literal: checkLiteral,
+ CallExpression: checkFunction,
+ NewExpression: checkFunction
};
}
diff --git a/tools/eslint/lib/rules/no-return-assign.js b/tools/eslint/lib/rules/no-return-assign.js
index 0d9e0b4b07..38fc1cb0eb 100644
--- a/tools/eslint/lib/rules/no-return-assign.js
+++ b/tools/eslint/lib/rules/no-return-assign.js
@@ -8,26 +8,19 @@
// Helpers
//------------------------------------------------------------------------------
-/**
- * Checks whether or not a node is an `AssignmentExpression`.
- * @param {Node|null} node - A node to check.
- * @returns {boolean} Whether or not the node is an `AssignmentExpression`.
- */
-function isAssignment(node) {
- return node && node.type === "AssignmentExpression";
-}
+var SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/;
/**
* Checks whether or not a node is enclosed in parentheses.
* @param {Node|null} node - A node to check.
- * @param {RuleContext} context - The current context.
+ * @param {sourceCode} sourceCode - The ESLint SourceCode object.
* @returns {boolean} Whether or not the node is enclosed in parentheses.
*/
-function isEnclosedInParens(node, context) {
- var prevToken = context.getTokenBefore(node);
- var nextToken = context.getTokenAfter(node);
+function isEnclosedInParens(node, sourceCode) {
+ var prevToken = sourceCode.getTokenBefore(node);
+ var nextToken = sourceCode.getTokenAfter(node);
- return prevToken.value === "(" && nextToken.value === ")";
+ return prevToken && prevToken.value === "(" && nextToken && nextToken.value === ")";
}
//------------------------------------------------------------------------------
@@ -51,32 +44,33 @@ module.exports = {
create: function(context) {
var always = (context.options[0] || "except-parens") !== "except-parens";
-
- /**
- * Check whether return statement contains assignment
- * @param {ASTNode} nodeToCheck node to check
- * @param {ASTNode} nodeToReport node to report
- * @param {string} message message to report
- * @returns {void}
- * @private
- */
- function checkForAssignInReturn(nodeToCheck, nodeToReport, message) {
- if (isAssignment(nodeToCheck) && (always || !isEnclosedInParens(nodeToCheck, context))) {
- context.report(nodeToReport, message);
- }
- }
+ var sourceCode = context.getSourceCode();
return {
- ReturnStatement: function(node) {
- var message = "Return statement should not contain assignment.";
+ AssignmentExpression: function(node) {
+ if (!always && isEnclosedInParens(node, sourceCode)) {
+ return;
+ }
- checkForAssignInReturn(node.argument, node, message);
- },
- ArrowFunctionExpression: function(node) {
- if (node.body.type !== "BlockStatement") {
- var message = "Arrow function should not return assignment.";
+ var parent = node.parent;
+
+ // Find ReturnStatement or ArrowFunctionExpression in ancestors.
+ while (parent && !SENTINEL_TYPE.test(parent.type)) {
+ node = parent;
+ parent = parent.parent;
+ }
- checkForAssignInReturn(node.body, node, message);
+ // Reports.
+ if (parent && parent.type === "ReturnStatement") {
+ context.report({
+ node: parent,
+ message: "Return statement should not contain assignment."
+ });
+ } else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === node) {
+ context.report({
+ node: parent,
+ message: "Arrow function should not return assignment."
+ });
}
}
};
diff --git a/tools/eslint/lib/rules/no-script-url.js b/tools/eslint/lib/rules/no-script-url.js
index 0605cd8642..1985cf3b95 100644
--- a/tools/eslint/lib/rules/no-script-url.js
+++ b/tools/eslint/lib/rules/no-script-url.js
@@ -14,7 +14,7 @@
module.exports = {
meta: {
docs: {
- description: "disallow `javascript",
+ description: "disallow `javascript:` urls",
category: "Best Practices",
recommended: false
},
diff --git a/tools/eslint/lib/rules/no-sequences.js b/tools/eslint/lib/rules/no-sequences.js
index ea20a4b955..b0d318c7d0 100644
--- a/tools/eslint/lib/rules/no-sequences.js
+++ b/tools/eslint/lib/rules/no-sequences.js
@@ -21,6 +21,7 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
/**
* Parts of the grammar that are required to have parens.
@@ -30,7 +31,8 @@ module.exports = {
IfStatement: "test",
SwitchStatement: "discriminant",
WhileStatement: "test",
- WithStatement: "object"
+ WithStatement: "object",
+ ArrowFunctionExpression: "body"
// Omitting CallExpression - commas are parsed as argument separators
// Omitting NewExpression - commas are parsed as argument separators
@@ -55,8 +57,8 @@ module.exports = {
* @returns {boolean} True if the node has a paren on each side.
*/
function isParenthesised(node) {
- var previousToken = context.getTokenBefore(node),
- nextToken = context.getTokenAfter(node);
+ var previousToken = sourceCode.getTokenBefore(node),
+ nextToken = sourceCode.getTokenAfter(node);
return previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@@ -69,8 +71,8 @@ module.exports = {
* @returns {boolean} True if two parens surround the node on each side.
*/
function isParenthesisedTwice(node) {
- var previousToken = context.getTokenBefore(node, 1),
- nextToken = context.getTokenAfter(node, 1);
+ var previousToken = sourceCode.getTokenBefore(node, 1),
+ nextToken = sourceCode.getTokenAfter(node, 1);
return isParenthesised(node) && previousToken && nextToken &&
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
@@ -97,7 +99,7 @@ module.exports = {
}
}
- var child = context.getTokenAfter(node.expressions[0]);
+ var child = sourceCode.getTokenAfter(node.expressions[0]);
context.report(node, child.loc.start, "Unexpected use of comma operator.");
}
diff --git a/tools/eslint/lib/rules/no-unexpected-multiline.js b/tools/eslint/lib/rules/no-unexpected-multiline.js
index c066673301..af0beb2c4d 100644
--- a/tools/eslint/lib/rules/no-unexpected-multiline.js
+++ b/tools/eslint/lib/rules/no-unexpected-multiline.js
@@ -24,6 +24,8 @@ module.exports = {
var PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access.";
var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal.";
+ var sourceCode = context.getSourceCode();
+
/**
* Check to see if there is a newline between the node and the following open bracket
* line's expression
@@ -34,12 +36,12 @@ module.exports = {
*/
function checkForBreakAfter(node, msg) {
var nodeExpressionEnd = node;
- var openParen = context.getTokenAfter(node);
+ var openParen = sourceCode.getTokenAfter(node);
// Move along until the end of the wrapped expression
while (openParen.value === ")") {
nodeExpressionEnd = openParen;
- openParen = context.getTokenAfter(nodeExpressionEnd);
+ openParen = sourceCode.getTokenAfter(nodeExpressionEnd);
}
if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {
diff --git a/tools/eslint/lib/rules/no-unsafe-finally.js b/tools/eslint/lib/rules/no-unsafe-finally.js
index 55ea2971f8..8c3815459c 100644
--- a/tools/eslint/lib/rules/no-unsafe-finally.js
+++ b/tools/eslint/lib/rules/no-unsafe-finally.js
@@ -9,7 +9,10 @@
// Helpers
//------------------------------------------------------------------------------
-var SENTINEL_NODE_TYPE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/;
+var SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/;
+var SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/;
+var SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/;
+
//------------------------------------------------------------------------------
// Rule Definition
@@ -18,9 +21,9 @@ var SENTINEL_NODE_TYPE = /^(?:Program|(?:Function|Class)(?:Declaration|Expressio
module.exports = {
meta: {
docs: {
- description: "disallow control flow statements in finally blocks",
+ description: "disallow control flow statements in `finally` blocks",
category: "Possible Errors",
- recommended: false
+ recommended: true
}
},
create: function(context) {
@@ -39,11 +42,29 @@ module.exports = {
* Climbs up the tree if the node is not a sentinel node
*
* @param {ASTNode} node - node to check.
+ * @param {String} label - label of the break or continue statement
* @returns {Boolean} - return whether the node is a finally block or a sentinel node
*/
- function isInFinallyBlock(node) {
- while (node && !SENTINEL_NODE_TYPE.test(node.type)) {
+ function isInFinallyBlock(node, label) {
+ var labelInside = false;
+ var sentinelNodeType;
+
+ if (node.type === "BreakStatement" && !node.label) {
+ sentinelNodeType = SENTINEL_NODE_TYPE_BREAK;
+ } else if (node.type === "ContinueStatement") {
+ sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE;
+ } else {
+ sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW;
+ }
+
+ while (node && !sentinelNodeType.test(node.type)) {
+ if (node.parent.label && label && (node.parent.label.name === label.name)) {
+ labelInside = true;
+ }
if (isFinallyBlock(node)) {
+ if (label && labelInside) {
+ return false;
+ }
return true;
}
node = node.parent;
@@ -58,7 +79,7 @@ module.exports = {
* @returns {void}
*/
function check(node) {
- if (isInFinallyBlock(node)) {
+ if (isInFinallyBlock(node, node.label)) {
context.report({
message: "Unsafe usage of " + node.type,
node: node,
diff --git a/tools/eslint/lib/rules/no-unused-vars.js b/tools/eslint/lib/rules/no-unused-vars.js
index 89d43c7bfd..39b7703c3c 100644
--- a/tools/eslint/lib/rules/no-unused-vars.js
+++ b/tools/eslint/lib/rules/no-unused-vars.js
@@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
var lodash = require("lodash");
+var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
@@ -95,6 +96,8 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
+ var STATEMENT_TYPE = /(?:Statement|Declaration)$/;
+
/**
* Determines if a given variable is being exported from a module.
* @param {Variable} variable - EScope variable object.
@@ -124,7 +127,7 @@ module.exports = {
/**
* Determines if a reference is a read operation.
* @param {Reference} ref - An escope Reference
- * @returns {Boolean} whether the given reference represents a read operation
+ * @returns {boolean} whether the given reference represents a read operation
* @private
*/
function isReadRef(ref) {
@@ -153,10 +156,203 @@ module.exports = {
}
/**
+ * Checks the position of given nodes.
+ *
+ * @param {ASTNode} inner - A node which is expected as inside.
+ * @param {ASTNode} outer - A node which is expected as outside.
+ * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
+ */
+ function isInside(inner, outer) {
+ return (
+ inner.range[0] >= outer.range[0] &&
+ inner.range[1] <= outer.range[1]
+ );
+ }
+
+ /**
+ * If a given reference is left-hand side of an assignment, this gets
+ * the right-hand side node of the assignment.
+ *
+ * @param {escope.Reference} ref - A reference to check.
+ * @param {ASTNode} prevRhsNode - The previous RHS node.
+ * @returns {ASTNode} The RHS node.
+ */
+ function getRhsNode(ref, prevRhsNode) {
+ var id = ref.identifier;
+ var parent = id.parent;
+ var granpa = parent.parent;
+ var refScope = ref.from.variableScope;
+ var varScope = ref.resolved.scope.variableScope;
+ var canBeUsedLater = refScope !== varScope;
+
+ /*
+ * Inherits the previous node if this reference is in the node.
+ * This is for `a = a + a`-like code.
+ */
+ if (prevRhsNode && isInside(id, prevRhsNode)) {
+ return prevRhsNode;
+ }
+
+ if (parent.type === "AssignmentExpression" &&
+ granpa.type === "ExpressionStatement" &&
+ id === parent.left &&
+ !canBeUsedLater
+ ) {
+ return parent.right;
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether a given function node is stored to somewhere or not.
+ * If the function node is stored, the function can be used later.
+ *
+ * @param {ASTNode} funcNode - A function node to check.
+ * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
+ * @returns {boolean} `true` if under the following conditions:
+ * - the funcNode is assigned to a variable.
+ * - the funcNode is bound as an argument of a function call.
+ * - the function is bound to a property and the object satisfies above conditions.
+ */
+ function isStorableFunction(funcNode, rhsNode) {
+ var node = funcNode;
+ var parent = funcNode.parent;
+
+ while (parent && isInside(parent, rhsNode)) {
+ switch (parent.type) {
+ case "SequenceExpression":
+ if (parent.expressions[parent.expressions.length - 1] !== node) {
+ return false;
+ }
+ break;
+
+ case "CallExpression":
+ case "NewExpression":
+ return parent.callee !== node;
+
+ case "AssignmentExpression":
+ case "TaggedTemplateExpression":
+ case "YieldExpression":
+ return true;
+
+ default:
+ if (STATEMENT_TYPE.test(parent.type)) {
+
+ /*
+ * If it encountered statements, this is a complex pattern.
+ * Since analyzeing complex patterns is hard, this returns `true` to avoid false positive.
+ */
+ return true;
+ }
+ }
+
+ node = parent;
+ parent = parent.parent;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether a given Identifier node exists inside of a function node which can be used later.
+ *
+ * "can be used later" means:
+ * - the function is assigned to a variable.
+ * - the function is bound to a property and the object can be used later.
+ * - the function is bound as an argument of a function call.
+ *
+ * If a reference exists in a function which can be used later, the reference is read when the function is called.
+ *
+ * @param {ASTNode} id - An Identifier node to check.
+ * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
+ * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
+ */
+ function isInsideOfStorableFunction(id, rhsNode) {
+ var funcNode = astUtils.getUpperFunction(id);
+
+ return (
+ funcNode &&
+ isInside(funcNode, rhsNode) &&
+ isStorableFunction(funcNode, rhsNode)
+ );
+ }
+
+ /**
+ * Checks whether a given reference is a read to update itself or not.
+ *
+ * @param {escope.Reference} ref - A reference to check.
+ * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
+ * @returns {boolean} The reference is a read to update itself.
+ */
+ function isReadForItself(ref, rhsNode) {
+ var id = ref.identifier;
+ var parent = id.parent;
+ var granpa = parent.parent;
+
+ return ref.isRead() && (
+
+ // self update. e.g. `a += 1`, `a++`
+ (
+ parent.type === "AssignmentExpression" &&
+ granpa.type === "ExpressionStatement" &&
+ parent.left === id
+ ) ||
+ (
+ parent.type === "UpdateExpression" &&
+ granpa.type === "ExpressionStatement"
+ ) ||
+
+ // in RHS of an assignment for itself. e.g. `a = a + 1`
+ (
+ rhsNode &&
+ isInside(id, rhsNode) &&
+ !isInsideOfStorableFunction(id, rhsNode)
+ )
+ );
+ }
+
+ /**
+ * Determine if an identifier is used either in for-in loops.
+ *
+ * @param {Reference} ref - The reference to check.
+ * @returns {boolean} whether reference is used in the for-in loops
+ * @private
+ */
+ function isForInRef(ref) {
+ var target = ref.identifier.parent;
+
+
+ // "for (var ...) { return; }"
+ if (target.type === "VariableDeclarator") {
+ target = target.parent.parent;
+ }
+
+ if (target.type !== "ForInStatement") {
+ return false;
+ }
+
+ // "for (...) { return; }"
+ if (target.body.type === "BlockStatement") {
+ target = target.body.body[0];
+
+ // "for (...) return;"
+ } else {
+ target = target.body;
+ }
+
+ // For empty loop body
+ if (!target) {
+ return false;
+ }
+
+ return target.type === "ReturnStatement";
+ }
+
+ /**
* Determines if the variable is used.
* @param {Variable} variable - The variable to check.
- * @param {Reference[]} references - The variable references to check.
* @returns {boolean} True if the variable is used
+ * @private
*/
function isUsedVariable(variable) {
var functionNodes = variable.defs.filter(function(def) {
@@ -164,10 +360,23 @@ module.exports = {
}).map(function(def) {
return def.node;
}),
- isFunctionDefinition = functionNodes.length > 0;
+ isFunctionDefinition = functionNodes.length > 0,
+ rhsNode = null;
return variable.references.some(function(ref) {
- return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes));
+ if (isForInRef(ref)) {
+ return true;
+ }
+
+ var forItself = isReadForItself(ref, rhsNode);
+
+ rhsNode = getRhsNode(ref, rhsNode);
+
+ return (
+ isReadRef(ref) &&
+ !forItself &&
+ !(isFunctionDefinition && isSelfReference(ref, functionNodes))
+ );
});
}
@@ -268,6 +477,7 @@ module.exports = {
* @param {escope.Variable} variable - A variable to get.
* @param {ASTNode} comment - A comment node which includes the variable name.
* @returns {number} The index of the variable name's location.
+ * @private
*/
function getColumnInComment(variable, comment) {
var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g");
@@ -287,6 +497,7 @@ module.exports = {
*
* @param {escope.Variable} variable - A variable to get its location.
* @returns {{line: number, column: number}} The location object for the variable.
+ * @private
*/
function getLocation(variable) {
var comment = variable.eslintExplicitGlobalComment;
diff --git a/tools/eslint/lib/rules/no-useless-call.js b/tools/eslint/lib/rules/no-useless-call.js
index eb14f0baf9..49cbbc5401 100644
--- a/tools/eslint/lib/rules/no-useless-call.js
+++ b/tools/eslint/lib/rules/no-useless-call.js
@@ -32,12 +32,12 @@ function isCallOrNonVariadicApply(node) {
* Checks whether or not the tokens of two given nodes are same.
* @param {ASTNode} left - A node 1 to compare.
* @param {ASTNode} right - A node 2 to compare.
- * @param {RuleContext} context - The ESLint rule context object.
+ * @param {SourceCode} sourceCode - The ESLint source code object.
* @returns {boolean} the source code for the given node.
*/
-function equalTokens(left, right, context) {
- var tokensL = context.getTokens(left);
- var tokensR = context.getTokens(right);
+function equalTokens(left, right, sourceCode) {
+ var tokensL = sourceCode.getTokens(left);
+ var tokensR = sourceCode.getTokens(right);
if (tokensL.length !== tokensR.length) {
return false;
@@ -57,14 +57,14 @@ function equalTokens(left, right, context) {
* Checks whether or not `thisArg` is not changed by `.call()`/`.apply()`.
* @param {ASTNode|null} expectedThis - The node that is the owner of the applied function.
* @param {ASTNode} thisArg - The node that is given to the first argument of the `.call()`/`.apply()`.
- * @param {RuleContext} context - The ESLint rule context object.
+ * @param {SourceCode} sourceCode - The ESLint source code object.
* @returns {boolean} Whether or not `thisArg` is not changed by `.call()`/`.apply()`.
*/
-function isValidThisArg(expectedThis, thisArg, context) {
+function isValidThisArg(expectedThis, thisArg, sourceCode) {
if (!expectedThis) {
return astUtils.isNullOrUndefined(thisArg);
}
- return equalTokens(expectedThis, thisArg, context);
+ return equalTokens(expectedThis, thisArg, sourceCode);
}
//------------------------------------------------------------------------------
@@ -83,6 +83,8 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
+
return {
CallExpression: function(node) {
if (!isCallOrNonVariadicApply(node)) {
@@ -93,7 +95,7 @@ module.exports = {
var expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
var thisArg = node.arguments[0];
- if (isValidThisArg(expectedThis, thisArg, context)) {
+ if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
context.report(
node,
"unnecessary '.{{name}}()'.",
diff --git a/tools/eslint/lib/rules/no-useless-computed-key.js b/tools/eslint/lib/rules/no-useless-computed-key.js
index 0894fb7ca4..2e0ac18019 100644
--- a/tools/eslint/lib/rules/no-useless-computed-key.js
+++ b/tools/eslint/lib/rules/no-useless-computed-key.js
@@ -19,6 +19,8 @@ module.exports = {
}
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
+
return {
Property: function(node) {
if (!node.computed) {
@@ -29,7 +31,7 @@ module.exports = {
nodeType = typeof key.value;
if (key.type === "Literal" && (nodeType === "string" || nodeType === "number")) {
- context.report(node, MESSAGE_UNNECESSARY_COMPUTED, { property: context.getSource(key) });
+ context.report(node, MESSAGE_UNNECESSARY_COMPUTED, { property: sourceCode.getText(key) });
}
}
};
diff --git a/tools/eslint/lib/rules/no-useless-concat.js b/tools/eslint/lib/rules/no-useless-concat.js
index ce9589d488..8569d4276a 100644
--- a/tools/eslint/lib/rules/no-useless-concat.js
+++ b/tools/eslint/lib/rules/no-useless-concat.js
@@ -67,6 +67,8 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
+
return {
BinaryExpression: function(node) {
@@ -85,10 +87,10 @@ module.exports = {
) {
// move warning location to operator
- var operatorToken = context.getTokenAfter(left);
+ var operatorToken = sourceCode.getTokenAfter(left);
while (operatorToken.value !== "+") {
- operatorToken = context.getTokenAfter(operatorToken);
+ operatorToken = sourceCode.getTokenAfter(operatorToken);
}
context.report(
diff --git a/tools/eslint/lib/rules/no-useless-rename.js b/tools/eslint/lib/rules/no-useless-rename.js
new file mode 100644
index 0000000000..ec2282784b
--- /dev/null
+++ b/tools/eslint/lib/rules/no-useless-rename.js
@@ -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
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/object-curly-newline.js b/tools/eslint/lib/rules/object-curly-newline.js
new file mode 100644
index 0000000000..aaa4af02a2
--- /dev/null
+++ b/tools/eslint/lib/rules/object-curly-newline.js
@@ -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
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/object-curly-spacing.js b/tools/eslint/lib/rules/object-curly-spacing.js
index 11224bbdd1..e5dfb8d036 100644
--- a/tools/eslint/lib/rules/object-curly-spacing.js
+++ b/tools/eslint/lib/rules/object-curly-spacing.js
@@ -171,7 +171,7 @@ module.exports = {
closingCurlyBraceMustBeSpaced = (
options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
- options.objectsInObjectsException && penultimateType === "ObjectExpression"
+ options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern")
) ? !options.spaced : options.spaced;
lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last);
diff --git a/tools/eslint/lib/rules/object-property-newline.js b/tools/eslint/lib/rules/object-property-newline.js
new file mode 100644
index 0000000000..eb96152bb1
--- /dev/null
+++ b/tools/eslint/lib/rules/object-property-newline.js
@@ -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
+ });
+ }
+ }
+ }
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/object-shorthand.js b/tools/eslint/lib/rules/object-shorthand.js
index 4c7c066bcd..a528c00d38 100644
--- a/tools/eslint/lib/rules/object-shorthand.js
+++ b/tools/eslint/lib/rules/object-shorthand.js
@@ -23,6 +23,8 @@ module.exports = {
recommended: false
},
+ fixable: "code",
+
schema: {
anyOf: [
{
@@ -39,6 +41,25 @@ module.exports = {
type: "array",
items: [
{
+ enum: ["always", "methods", "properties"]
+ },
+ {
+ type: "object",
+ properties: {
+ avoidQuotes: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ minItems: 0,
+ maxItems: 2
+ },
+ {
+ type: "array",
+ items: [
+ {
enum: ["always", "methods"]
},
{
@@ -46,6 +67,9 @@ module.exports = {
properties: {
ignoreConstructors: {
type: "boolean"
+ },
+ avoidQuotes: {
+ type: "boolean"
}
},
additionalProperties: false
@@ -66,6 +90,7 @@ module.exports = {
var PARAMS = context.options[1] || {};
var IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors;
+ var AVOID_QUOTES = PARAMS.avoidQuotes;
//--------------------------------------------------------------------------
// Helpers
@@ -83,6 +108,15 @@ module.exports = {
return firstChar === firstChar.toUpperCase();
}
+ /**
+ * Checks whether a node is a string literal.
+ * @param {ASTNode} node - Any AST node.
+ * @returns {boolean} `true` if it is a string literal.
+ */
+ function isStringLiteral(node) {
+ return node.type === "Literal" && typeof node.value === "string";
+ }
+
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -97,45 +131,127 @@ module.exports = {
return;
}
- // if we're "never" and concise we should warn now
- if (APPLY_NEVER && isConciseProperty) {
- type = node.method ? "method" : "property";
- context.report(node, "Expected longform " + type + " syntax.");
- }
-
- // at this point if we're concise or if we're "never" we can leave
- if (APPLY_NEVER || isConciseProperty) {
+ // getters and setters are ignored
+ if (node.kind === "get" || node.kind === "set") {
return;
}
// only computed methods can fail the following checks
- if (!APPLY_TO_METHODS && node.computed) {
+ if (node.computed && node.value.type !== "FunctionExpression") {
return;
}
- // getters and setters are ignored
- if (node.kind === "get" || node.kind === "set") {
+ //--------------------------------------------------------------
+ // Checks for property/method shorthand.
+ if (isConciseProperty) {
+
+ // if we're "never" and concise we should warn now
+ if (APPLY_NEVER) {
+ type = node.method ? "method" : "property";
+ context.report({
+ node: node,
+ message: "Expected longform " + type + " syntax.",
+ fix: function(fixer) {
+ if (node.method) {
+ if (node.value.generator) {
+ return fixer.replaceTextRange([node.range[0], node.key.range[1]], node.key.name + ": function*");
+ }
+
+ return fixer.insertTextAfter(node.key, ": function");
+ }
+
+ return fixer.insertTextAfter(node.key, ": " + node.key.name);
+ }
+ });
+ }
+
+ // {'xyz'() {}} should be written as {'xyz': function() {}}
+ if (AVOID_QUOTES && isStringLiteral(node.key)) {
+ context.report({
+ node: node,
+ message: "Expected longform method syntax for string literal keys.",
+ fix: function(fixer) {
+ if (node.computed) {
+ return fixer.insertTextAfterRange([node.key.range[0], node.key.range[1] + 1], ": function");
+ }
+
+ return fixer.insertTextAfter(node.key, ": function");
+ }
+ });
+ }
+
return;
}
+ //--------------------------------------------------------------
+ // Checks for longform properties.
if (node.value.type === "FunctionExpression" && !node.value.id && APPLY_TO_METHODS) {
if (IGNORE_CONSTRUCTORS && isConstructor(node.key.name)) {
return;
}
+ if (AVOID_QUOTES && isStringLiteral(node.key)) {
+ return;
+ }
+
+ // {[x]: function(){}} should be written as {[x]() {}}
+ if (node.computed) {
+ context.report({
+ node: node,
+ message: "Expected method shorthand.",
+ fix: function(fixer) {
+ if (node.value.generator) {
+ return fixer.replaceTextRange(
+ [node.key.range[0], node.value.range[0] + "function*".length],
+ "*[" + node.key.name + "]"
+ );
+ }
+
+ return fixer.removeRange([node.key.range[1] + 1, node.value.range[0] + "function".length]);
+ }
+ });
+ return;
+ }
// {x: function(){}} should be written as {x() {}}
- context.report(node, "Expected method shorthand.");
+ context.report({
+ node: node,
+ message: "Expected method shorthand.",
+ fix: function(fixer) {
+ if (node.value.generator) {
+ return fixer.replaceTextRange(
+ [node.key.range[0], node.value.range[0] + "function*".length],
+ "*" + node.key.name
+ );
+ }
+
+ return fixer.removeRange([node.key.range[1], node.value.range[0] + "function".length]);
+ }
+ });
} 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.");
+ context.report({
+ node: node,
+ message: "Expected property shorthand.",
+ fix: function(fixer) {
+ return fixer.replaceText(node, node.value.name);
+ }
+ });
} else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) {
+ if (AVOID_QUOTES) {
+ return;
+ }
// {"x": x} should be written as {x}
- context.report(node, "Expected property shorthand.");
+ context.report({
+ node: node,
+ message: "Expected property shorthand.",
+ fix: function(fixer) {
+ return fixer.replaceText(node, node.value.name);
+ }
+ });
}
}
};
-
}
};
diff --git a/tools/eslint/lib/rules/one-var.js b/tools/eslint/lib/rules/one-var.js
index 805cec3654..2bd49f511f 100644
--- a/tools/eslint/lib/rules/one-var.js
+++ b/tools/eslint/lib/rules/one-var.js
@@ -286,6 +286,9 @@ module.exports = {
context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables.");
}
if (options[type].uninitialized === MODE_ALWAYS) {
+ if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
+ return;
+ }
context.report(node, "Combine this with the previous '" + type + "' statement with uninitialized variables.");
}
}
diff --git a/tools/eslint/lib/rules/operator-linebreak.js b/tools/eslint/lib/rules/operator-linebreak.js
index 85f90b908f..8f17155b86 100644
--- a/tools/eslint/lib/rules/operator-linebreak.js
+++ b/tools/eslint/lib/rules/operator-linebreak.js
@@ -57,6 +57,8 @@ module.exports = {
styleOverrides[":"] = "before";
}
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -69,8 +71,8 @@ module.exports = {
* @returns {void}
*/
function validateNode(node, leftSide) {
- var leftToken = context.getLastToken(leftSide);
- var operatorToken = context.getTokenAfter(leftToken);
+ var leftToken = sourceCode.getLastToken(leftSide);
+ var operatorToken = sourceCode.getTokenAfter(leftToken);
// When the left part of a binary expression is a single expression wrapped in
// parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
@@ -79,10 +81,10 @@ module.exports = {
// should be the token right after that.
while (operatorToken.value === ")") {
leftToken = operatorToken;
- operatorToken = context.getTokenAfter(operatorToken);
+ operatorToken = sourceCode.getTokenAfter(operatorToken);
}
- var rightToken = context.getTokenAfter(operatorToken);
+ var rightToken = sourceCode.getTokenAfter(operatorToken);
var operator = operatorToken.value;
var operatorStyleOverride = styleOverrides[operator];
var style = operatorStyleOverride || globalStyle;
diff --git a/tools/eslint/lib/rules/padded-blocks.js b/tools/eslint/lib/rules/padded-blocks.js
index 39a2b07c8d..7ec24c65d7 100644
--- a/tools/eslint/lib/rules/padded-blocks.js
+++ b/tools/eslint/lib/rules/padded-blocks.js
@@ -17,6 +17,8 @@ module.exports = {
recommended: false
},
+ fixable: "whitespace",
+
schema: [
{
oneOf: [
@@ -164,6 +166,9 @@ module.exports = {
context.report({
node: node,
loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
+ fix: function(fixer) {
+ return fixer.insertTextAfter(openBrace, "\n");
+ },
message: ALWAYS_MESSAGE
});
}
@@ -171,23 +176,36 @@ module.exports = {
context.report({
node: node,
loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
+ fix: function(fixer) {
+ return fixer.insertTextBefore(closeBrace, "\n");
+ },
message: ALWAYS_MESSAGE
});
}
} else {
if (blockHasTopPadding) {
+ var nextToken = sourceCode.getTokenOrCommentAfter(openBrace);
+
context.report({
node: node,
loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
+ fix: function(fixer) {
+ return fixer.replaceTextRange([openBrace.end, nextToken.start - nextToken.loc.start.column], "\n");
+ },
message: NEVER_MESSAGE
});
}
if (blockHasBottomPadding) {
+ var previousToken = sourceCode.getTokenOrCommentBefore(closeBrace);
+
context.report({
node: node,
loc: {line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
- message: NEVER_MESSAGE
+ message: NEVER_MESSAGE,
+ fix: function(fixer) {
+ return fixer.replaceTextRange([previousToken.end, closeBrace.start - closeBrace.loc.start.column], "\n");
+ }
});
}
}
diff --git a/tools/eslint/lib/rules/prefer-const.js b/tools/eslint/lib/rules/prefer-const.js
index 668453520f..7b8ac42519 100644
--- a/tools/eslint/lib/rules/prefer-const.js
+++ b/tools/eslint/lib/rules/prefer-const.js
@@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
var Map = require("es6-map");
+var lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@@ -62,45 +63,72 @@ function canBecomeVariableDeclaration(identifier) {
}
/**
- * Gets the WriteReference of a given variable if the variable should be
- * declared as const.
+ * Gets an identifier node of a given variable.
+ *
+ * If the initialization exists or one or more reading references exist before
+ * the first assignment, the identifier node is the node of the declaration.
+ * Otherwise, the identifier node is the node of the first assignment.
+ *
+ * If the variable should not change to const, this function returns null.
+ * - If the variable is reassigned.
+ * - If the variable is never initialized and assigned.
+ * - If the variable is initialized in a different scope from the declaration.
+ * - If the unique assignment of the variable cannot change to a declaration.
*
* @param {escope.Variable} variable - A variable to get.
- * @returns {escope.Reference|null} The singular WriteReference or null.
+ * @param {boolean} ignoreReadBeforeAssign -
+ * The value of `ignoreReadBeforeAssign` option.
+ * @returns {ASTNode|null}
+ * An Identifier node if the variable should change to const.
+ * Otherwise, null.
*/
-function getWriteReferenceIfShouldBeConst(variable) {
+function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
if (variable.eslintUsed) {
return null;
}
- // Finds the singular WriteReference.
- var retv = null;
+ // Finds the unique WriteReference.
+ var writer = null;
+ var isReadBeforeInit = false;
var references = variable.references;
for (var i = 0; i < references.length; ++i) {
var reference = references[i];
if (reference.isWrite()) {
- var isReassigned = Boolean(
- retv && retv.identifier !== reference.identifier
+ var isReassigned = (
+ writer !== null &&
+ writer.identifier !== reference.identifier
);
if (isReassigned) {
return null;
}
- retv = reference;
+ writer = reference;
+
+ } else if (reference.isRead() && writer === null) {
+ if (ignoreReadBeforeAssign) {
+ return null;
+ }
+ isReadBeforeInit = true;
}
}
- // Checks the writer is located in the same scope and can be modified to
- // const.
- var isSameScopeAndCanBecomeVariableDeclaration = Boolean(
- retv &&
- retv.from === variable.scope &&
- canBecomeVariableDeclaration(retv.identifier)
+ // If the assignment is from a different scope, ignore it.
+ // If the assignment cannot change to a declaration, ignore it.
+ var shouldBeConst = (
+ writer !== null &&
+ writer.from === variable.scope &&
+ canBecomeVariableDeclaration(writer.identifier)
);
- return isSameScopeAndCanBecomeVariableDeclaration ? retv : null;
+ if (!shouldBeConst) {
+ return null;
+ }
+ if (isReadBeforeInit) {
+ return variable.defs[0].name;
+ }
+ return writer.identifier;
}
/**
@@ -136,15 +164,17 @@ function getDestructuringHost(reference) {
* destructuring.
*
* @param {escope.Variable[]} variables - Variables to group by destructuring.
- * @returns {Map<ASTNode, (escope.Reference|null)[]>} Grouped references.
+ * @param {boolean} ignoreReadBeforeAssign -
+ * The value of `ignoreReadBeforeAssign` option.
+ * @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes.
*/
-function groupByDestructuring(variables) {
- var writersMap = new Map();
+function groupByDestructuring(variables, ignoreReadBeforeAssign) {
+ var identifierMap = new Map();
for (var i = 0; i < variables.length; ++i) {
var variable = variables[i];
var references = variable.references;
- var writer = getWriteReferenceIfShouldBeConst(variable);
+ var identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
var prevId = null;
for (var j = 0; j < references.length; ++j) {
@@ -158,20 +188,38 @@ function groupByDestructuring(variables) {
}
prevId = id;
- // Add the writer into the destructuring group.
+ // Add the identifier node into the destructuring group.
var group = getDestructuringHost(reference);
if (group) {
- if (writersMap.has(group)) {
- writersMap.get(group).push(writer);
+ if (identifierMap.has(group)) {
+ identifierMap.get(group).push(identifier);
} else {
- writersMap.set(group, [writer]);
+ identifierMap.set(group, [identifier]);
}
}
}
}
- return writersMap;
+ return identifierMap;
+}
+
+/**
+ * Finds the nearest parent of node with a given type.
+ *
+ * @param {ASTNode} node ā€“ The node to search from.
+ * @param {string} type ā€“ The type field of the parent node.
+ * @param {function} shouldStop ā€“ a predicate that returns true if the traversal should stop, and false otherwise.
+ * @returns {ASTNode} The closest ancestor with the specified type; null if no such ancestor exists.
+ */
+function findUp(node, type, shouldStop) {
+ if (!node || shouldStop(node)) {
+ return null;
+ }
+ if (node.type === type) {
+ return node;
+ }
+ return findUp(node.parent, type, shouldStop);
}
//------------------------------------------------------------------------------
@@ -186,11 +234,14 @@ module.exports = {
recommended: false
},
+ fixable: "code",
+
schema: [
{
type: "object",
properties: {
- destructuring: {enum: ["any", "all"]}
+ destructuring: {enum: ["any", "all"]},
+ ignoreReadBeforeAssign: {type: "boolean"}
},
additionalProperties: false
}
@@ -200,22 +251,64 @@ module.exports = {
create: function(context) {
var options = context.options[0] || {};
var checkingMixedDestructuring = options.destructuring !== "all";
+ var ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
var variables = null;
/**
- * Reports a given reference.
+ * Reports a given Identifier node.
*
- * @param {escope.Reference} reference - A reference to report.
+ * @param {ASTNode} node - An Identifier node to report.
* @returns {void}
*/
- function report(reference) {
- var id = reference.identifier;
+ function report(node) {
+ var reportArgs = {
+ node: node,
+ message: "'{{name}}' is never reassigned. Use 'const' instead.",
+ data: node
+ },
+ varDeclParent = findUp(node, "VariableDeclaration", function(parentNode) {
+ return lodash.endsWith(parentNode.type, "Statement");
+ }),
+ isNormalVarDecl = (node.parent.parent.parent.type === "ForInStatement" ||
+ node.parent.parent.parent.type === "ForOfStatement" ||
+ node.parent.init),
+
+ isDestructuringVarDecl =
+
+ // {let {a} = obj} should be written as {const {a} = obj}
+ (node.parent.parent.type === "ObjectPattern" &&
+
+ // If options.destucturing is "all", then this warning will not occur unless
+ // every assignment in the destructuring should be const. In that case, it's safe
+ // to apply the fix. Otherwise, it's safe to apply the fix if there's only one
+ // assignment occurring. If there is more than one assignment and options.destructuring
+ // is not "all", then it's not clear how the developer would want to resolve the issue,
+ // so we should not attempt to do it programmatically.
+ (options.destructuring === "all" || node.parent.parent.properties.length === 1)) ||
+
+ // {let [a] = [1]} should be written as {const [a] = [1]}
+ (node.parent.type === "ArrayPattern" &&
+
+ // See note above about fixing multiple warnings at once.
+ (options.destructuring === "all" || node.parent.elements.length === 1));
+
+ if (varDeclParent &&
+ (isNormalVarDecl || isDestructuringVarDecl) &&
+
+ // If there are multiple variable declarations, like {let a = 1, b = 2}, then
+ // do not attempt to fix if one of the declarations should be `const`. It's
+ // too hard to know how the developer would want to automatically resolve the issue.
+ varDeclParent.declarations.length === 1) {
+
+ reportArgs.fix = function(fixer) {
+ return fixer.replaceTextRange(
+ [varDeclParent.start, varDeclParent.start + "let".length],
+ "const"
+ );
+ };
+ }
- context.report({
- node: id,
- message: "'{{name}}' is never reassigned, use 'const' instead.",
- data: id
- });
+ context.report(reportArgs);
}
/**
@@ -225,30 +318,30 @@ module.exports = {
* @returns {void}
*/
function checkVariable(variable) {
- var writer = getWriteReferenceIfShouldBeConst(variable);
+ var node = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
- if (writer) {
- report(writer);
+ if (node) {
+ report(node);
}
}
/**
- * Reports given references if all of the reference should be declared as
- * const.
+ * Reports given identifier nodes if all of the nodes should be declared
+ * as const.
*
- * The argument 'writers' is an array of references.
- * This reference is the result of
- * 'getWriteReferenceIfShouldBeConst(variable)', so it's nullable.
- * In simple declaration or assignment cases, the length of the array is 1.
- * In destructuring cases, the length of the array can be 2 or more.
+ * The argument 'nodes' is an array of Identifier nodes.
+ * This node is the result of 'getIdentifierIfShouldBeConst()', so it's
+ * nullable. In simple declaration or assignment cases, the length of
+ * the array is 1. In destructuring cases, the length of the array can
+ * be 2 or more.
*
- * @param {(escope.Reference|null)[]} writers - References which are grouped
- * by destructuring to report.
+ * @param {(escope.Reference|null)[]} nodes -
+ * References which are grouped by destructuring to report.
* @returns {void}
*/
- function checkGroup(writers) {
- if (writers.every(Boolean)) {
- writers.forEach(report);
+ function checkGroup(nodes) {
+ if (nodes.every(Boolean)) {
+ nodes.forEach(report);
}
}
@@ -261,7 +354,8 @@ module.exports = {
if (checkingMixedDestructuring) {
variables.forEach(checkVariable);
} else {
- groupByDestructuring(variables).forEach(checkGroup);
+ groupByDestructuring(variables, ignoreReadBeforeAssign)
+ .forEach(checkGroup);
}
variables = null;
diff --git a/tools/eslint/lib/rules/prefer-spread.js b/tools/eslint/lib/rules/prefer-spread.js
index 79c7eb0243..67f1e855b0 100644
--- a/tools/eslint/lib/rules/prefer-spread.js
+++ b/tools/eslint/lib/rules/prefer-spread.js
@@ -31,12 +31,12 @@ function isVariadicApplyCalling(node) {
* Checks whether or not the tokens of two given nodes are same.
* @param {ASTNode} left - A node 1 to compare.
* @param {ASTNode} right - A node 2 to compare.
- * @param {RuleContext} context - The ESLint rule context object.
+ * @param {SourceCode} sourceCode - The ESLint source code object.
* @returns {boolean} the source code for the given node.
*/
-function equalTokens(left, right, context) {
- var tokensL = context.getTokens(left);
- var tokensR = context.getTokens(right);
+function equalTokens(left, right, sourceCode) {
+ var tokensL = sourceCode.getTokens(left);
+ var tokensR = sourceCode.getTokens(right);
if (tokensL.length !== tokensR.length) {
return false;
@@ -82,6 +82,8 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
+
return {
CallExpression: function(node) {
if (!isVariadicApplyCalling(node)) {
@@ -92,7 +94,7 @@ module.exports = {
var expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
var thisArg = node.arguments[0];
- if (isValidThisArg(expectedThis, thisArg, context)) {
+ if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
context.report(node, "use the spread operator instead of the '.apply()'.");
}
}
diff --git a/tools/eslint/lib/rules/require-yield.js b/tools/eslint/lib/rules/require-yield.js
index 441d354ed8..cde7d8c2c4 100644
--- a/tools/eslint/lib/rules/require-yield.js
+++ b/tools/eslint/lib/rules/require-yield.js
@@ -14,7 +14,7 @@ module.exports = {
docs: {
description: "require generator functions to contain `yield`",
category: "ECMAScript 6",
- recommended: false
+ recommended: true
},
schema: []
diff --git a/tools/eslint/lib/rules/rest-spread-spacing.js b/tools/eslint/lib/rules/rest-spread-spacing.js
new file mode 100644
index 0000000000..7ffafa5319
--- /dev/null
+++ b/tools/eslint/lib/rules/rest-spread-spacing.js
@@ -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
+ };
+ }
+};
diff --git a/tools/eslint/lib/rules/semi-spacing.js b/tools/eslint/lib/rules/semi-spacing.js
index ea43e9243d..830044d2f3 100644
--- a/tools/eslint/lib/rules/semi-spacing.js
+++ b/tools/eslint/lib/rules/semi-spacing.js
@@ -59,7 +59,7 @@ module.exports = {
* @returns {boolean} True if the given token has leading space, false if not.
*/
function hasLeadingSpace(token) {
- var tokenBefore = context.getTokenBefore(token);
+ var tokenBefore = sourceCode.getTokenBefore(token);
return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
}
@@ -70,7 +70,7 @@ module.exports = {
* @returns {boolean} True if the given token has trailing space, false if not.
*/
function hasTrailingSpace(token) {
- var tokenAfter = context.getTokenAfter(token);
+ var tokenAfter = sourceCode.getTokenAfter(token);
return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
}
@@ -81,7 +81,7 @@ module.exports = {
* @returns {boolean} Whether or not the token is the last in its line.
*/
function isLastTokenInCurrentLine(token) {
- var tokenAfter = context.getTokenAfter(token);
+ var tokenAfter = sourceCode.getTokenAfter(token);
return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
}
@@ -92,7 +92,7 @@ module.exports = {
* @returns {boolean} Whether or not the token is the first in its line.
*/
function isFirstTokenInCurrentLine(token) {
- var tokenBefore = context.getTokenBefore(token);
+ var tokenBefore = sourceCode.getTokenBefore(token);
return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
}
@@ -103,7 +103,7 @@ module.exports = {
* @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
*/
function isBeforeClosingParen(token) {
- var nextToken = context.getTokenAfter(token);
+ var nextToken = sourceCode.getTokenAfter(token);
return (
nextToken &&
@@ -140,7 +140,7 @@ module.exports = {
loc: location,
message: "Unexpected whitespace before semicolon.",
fix: function(fixer) {
- var tokenBefore = context.getTokenBefore(token);
+ var tokenBefore = sourceCode.getTokenBefore(token);
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
@@ -167,7 +167,7 @@ module.exports = {
loc: location,
message: "Unexpected whitespace after semicolon.",
fix: function(fixer) {
- var tokenAfter = context.getTokenAfter(token);
+ var tokenAfter = sourceCode.getTokenAfter(token);
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
@@ -195,7 +195,7 @@ module.exports = {
* @returns {void}
*/
function checkNode(node) {
- var token = context.getLastToken(node);
+ var token = sourceCode.getLastToken(node);
checkSemicolonSpacing(token, node);
}
@@ -210,11 +210,11 @@ module.exports = {
ThrowStatement: checkNode,
ForStatement: function(node) {
if (node.init) {
- checkSemicolonSpacing(context.getTokenAfter(node.init), node);
+ checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
}
if (node.test) {
- checkSemicolonSpacing(context.getTokenAfter(node.test), node);
+ checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
}
}
};
diff --git a/tools/eslint/lib/rules/semi.js b/tools/eslint/lib/rules/semi.js
index e386084faf..d530725040 100644
--- a/tools/eslint/lib/rules/semi.js
+++ b/tools/eslint/lib/rules/semi.js
@@ -121,7 +121,7 @@ module.exports = {
return false;
}
- nextToken = context.getTokenAfter(lastToken);
+ nextToken = sourceCode.getTokenAfter(lastToken);
if (!nextToken) {
return true;
@@ -141,7 +141,7 @@ module.exports = {
* @returns {boolean} whether the node is in a one-liner block statement.
*/
function isOneLinerBlock(node) {
- var nextToken = context.getTokenAfter(node);
+ var nextToken = sourceCode.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") {
return false;
@@ -159,7 +159,7 @@ module.exports = {
* @returns {void}
*/
function checkForSemicolon(node) {
- var lastToken = context.getLastToken(node);
+ var lastToken = sourceCode.getLastToken(node);
if (never) {
if (isUnnecessarySemicolon(lastToken)) {
diff --git a/tools/eslint/lib/rules/space-before-blocks.js b/tools/eslint/lib/rules/space-before-blocks.js
index 7fb9d5cddc..468b320447 100644
--- a/tools/eslint/lib/rules/space-before-blocks.js
+++ b/tools/eslint/lib/rules/space-before-blocks.js
@@ -81,7 +81,7 @@ module.exports = {
* @returns {void} undefined.
*/
function checkPrecedingSpace(node) {
- var precedingToken = context.getTokenBefore(node),
+ var precedingToken = sourceCode.getTokenBefore(node),
hasSpace,
parent,
requireSpace;
@@ -133,9 +133,9 @@ module.exports = {
if (cases.length > 0) {
firstCase = cases[0];
- openingBrace = context.getTokenBefore(firstCase);
+ openingBrace = sourceCode.getTokenBefore(firstCase);
} else {
- openingBrace = context.getLastToken(node, 1);
+ openingBrace = sourceCode.getLastToken(node, 1);
}
checkPrecedingSpace(openingBrace);
diff --git a/tools/eslint/lib/rules/space-before-function-paren.js b/tools/eslint/lib/rules/space-before-function-paren.js
index d96cb4a608..2d26e41e4a 100644
--- a/tools/eslint/lib/rules/space-before-function-paren.js
+++ b/tools/eslint/lib/rules/space-before-function-paren.js
@@ -106,7 +106,7 @@ module.exports = {
while (rightToken.value !== "(") {
rightToken = sourceCode.getTokenAfter(rightToken);
}
- leftToken = context.getTokenBefore(rightToken);
+ leftToken = sourceCode.getTokenBefore(rightToken);
location = leftToken.loc.end;
if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) {
diff --git a/tools/eslint/lib/rules/space-infix-ops.js b/tools/eslint/lib/rules/space-infix-ops.js
index 862ff66fb5..bea82ba0b6 100644
--- a/tools/eslint/lib/rules/space-infix-ops.js
+++ b/tools/eslint/lib/rules/space-infix-ops.js
@@ -41,6 +41,8 @@ module.exports = {
"?", ":", ",", "**"
];
+ var sourceCode = context.getSourceCode();
+
/**
* Returns the first token which violates the rule
* @param {ASTNode} left - The left node of the main node
@@ -50,7 +52,7 @@ module.exports = {
*/
function getFirstNonSpacedToken(left, right) {
var op,
- tokens = context.getTokensBetween(left, right, 1);
+ tokens = sourceCode.getTokensBetween(left, right, 1);
for (var i = 1, l = tokens.length - 1; i < l; ++i) {
op = tokens[i];
@@ -78,8 +80,8 @@ module.exports = {
loc: culpritToken.loc.start,
message: "Infix operators must be spaced.",
fix: function(fixer) {
- var previousToken = context.getTokenBefore(culpritToken);
- var afterToken = context.getTokenAfter(culpritToken);
+ var previousToken = sourceCode.getTokenBefore(culpritToken);
+ var afterToken = sourceCode.getTokenAfter(culpritToken);
var fixString = "";
if (culpritToken.range[0] - previousToken.range[1] === 0) {
@@ -107,7 +109,7 @@ module.exports = {
var nonSpacedNode = getFirstNonSpacedToken(node.left, node.right);
if (nonSpacedNode) {
- if (!(int32Hint && context.getSource(node).substr(-2) === "|0")) {
+ if (!(int32Hint && sourceCode.getText(node).substr(-2) === "|0")) {
report(node, nonSpacedNode);
}
}
diff --git a/tools/eslint/lib/rules/space-unary-ops.js b/tools/eslint/lib/rules/space-unary-ops.js
index 0bb92af1e6..fdb1c03e98 100644
--- a/tools/eslint/lib/rules/space-unary-ops.js
+++ b/tools/eslint/lib/rules/space-unary-ops.js
@@ -43,6 +43,8 @@ module.exports = {
create: function(context) {
var options = context.options && Array.isArray(context.options) && context.options[0] || { words: true, nonwords: false };
+ var sourceCode = context.getSourceCode();
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -158,7 +160,7 @@ module.exports = {
* @returns {void}
*/
function checkForSpacesAfterYield(node) {
- var tokens = context.getFirstTokens(node, 3),
+ var tokens = sourceCode.getFirstTokens(node, 3),
word = "yield";
if (!node.argument || node.delegate) {
@@ -239,7 +241,7 @@ module.exports = {
* @returns {void}
*/
function checkForSpaces(node) {
- var tokens = context.getFirstTokens(node, 2),
+ var tokens = sourceCode.getFirstTokens(node, 2),
firstToken = tokens[0],
secondToken = tokens[1];
diff --git a/tools/eslint/lib/rules/strict.js b/tools/eslint/lib/rules/strict.js
index 4097a32793..45021517c7 100644
--- a/tools/eslint/lib/rules/strict.js
+++ b/tools/eslint/lib/rules/strict.js
@@ -23,7 +23,9 @@ var messages = {
unnecessary: "Unnecessary 'use strict' directive.",
module: "'use strict' is unnecessary inside of modules.",
implied: "'use strict' is unnecessary when implied strict mode is enabled.",
- unnecessaryInClasses: "'use strict' is unnecessary inside of classes."
+ unnecessaryInClasses: "'use strict' is unnecessary inside of classes.",
+ nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.",
+ wrap: "Wrap this function in a function with 'use strict' directive."
};
/**
@@ -53,6 +55,26 @@ function getUseStrictDirectives(statements) {
return directives;
}
+/**
+ * Checks whether a given parameter is a simple parameter.
+ *
+ * @param {ASTNode} node - A pattern node to check.
+ * @returns {boolean} `true` if the node is an Identifier node.
+ */
+function isSimpleParameter(node) {
+ return node.type === "Identifier";
+}
+
+/**
+ * Checks whether a given parameter list is a simple parameter list.
+ *
+ * @param {ASTNode[]} params - A parameter list to check.
+ * @returns {boolean} `true` if the every parameter is an Identifier node.
+ */
+function isSimpleParameterList(params) {
+ return params.every(isSimpleParameter);
+}
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -136,7 +158,9 @@ module.exports = {
isStrict = useStrictDirectives.length > 0;
if (isStrict) {
- if (isParentStrict) {
+ if (!isSimpleParameterList(node.params)) {
+ context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
+ } else if (isParentStrict) {
context.report(useStrictDirectives[0], messages.unnecessary);
} else if (isInClass) {
context.report(useStrictDirectives[0], messages.unnecessaryInClasses);
@@ -144,7 +168,11 @@ module.exports = {
reportAllExceptFirst(useStrictDirectives, messages.multiple);
} else if (isParentGlobal) {
- context.report(node, messages.function);
+ if (isSimpleParameterList(node.params)) {
+ context.report(node, messages.function);
+ } else {
+ context.report(node, messages.wrap);
+ }
}
scopes.push(isParentStrict || isStrict);
@@ -172,8 +200,13 @@ module.exports = {
if (mode === "function") {
enterFunctionInFunctionMode(node, useStrictDirectives);
- } else {
- reportAll(useStrictDirectives, messages[mode]);
+ } else if (useStrictDirectives.length > 0) {
+ if (isSimpleParameterList(node.params)) {
+ reportAll(useStrictDirectives, messages[mode]);
+ } else {
+ context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
+ reportAllExceptFirst(useStrictDirectives, messages.multiple);
+ }
}
}
diff --git a/tools/eslint/lib/rules/unicode-bom.js b/tools/eslint/lib/rules/unicode-bom.js
new file mode 100644
index 0000000000..a152b03ac9
--- /dev/null
+++ b/tools/eslint/lib/rules/unicode-bom.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Require or disallow Unicode BOM
+ * @author Andrew Johnston <https://github.com/ehjay>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: "require or disallow Unicode byte order mark (BOM)",
+ category: "Stylistic Issues",
+ recommended: false
+ },
+
+ fixable: "whitespace",
+
+ schema: [
+ {
+ enum: ["always", "never"]
+ }
+ ]
+ },
+
+ create: function(context) {
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ Program: function checkUnicodeBOM(node) {
+
+ var sourceCode = context.getSourceCode(),
+ location = {column: 0, line: 1},
+ requireBOM = context.options[0] || "never";
+
+ if (!sourceCode.hasBOM && (requireBOM === "always")) {
+ context.report({
+ node: node,
+ loc: location,
+ message: "Expected Unicode BOM (Byte Order Mark).",
+ fix: function(fixer) {
+ return fixer.insertTextBefore(node, "\uFEFF");
+ }
+ });
+ } else if (sourceCode.hasBOM && (requireBOM === "never")) {
+ context.report({
+ node: node,
+ loc: location,
+ message: "Unexpected Unicode BOM (Byte Order Mark).",
+ fix: function(fixer) {
+ return fixer.removeRange([-1, 0]);
+ }
+ });
+ }
+ }
+
+ };
+
+ }
+};
diff --git a/tools/eslint/lib/rules/valid-jsdoc.js b/tools/eslint/lib/rules/valid-jsdoc.js
index e7d6fdeadf..65ed539d55 100644
--- a/tools/eslint/lib/rules/valid-jsdoc.js
+++ b/tools/eslint/lib/rules/valid-jsdoc.js
@@ -340,14 +340,14 @@ module.exports = {
if (node.params) {
node.params.forEach(function(param, i) {
- var name = param.name;
-
if (param.type === "AssignmentPattern") {
- name = param.left.name;
+ param = param.left;
}
+ var name = param.name;
+
// TODO(nzakas): Figure out logical things to do with destructured, default, rest params
- if (param.type === "Identifier" || param.type === "AssignmentPattern") {
+ if (param.type === "Identifier") {
if (jsdocParams[i] && (name !== jsdocParams[i])) {
context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", {
name: name,
diff --git a/tools/eslint/lib/rules/vars-on-top.js b/tools/eslint/lib/rules/vars-on-top.js
index b44f77eb3f..25bef0411d 100644
--- a/tools/eslint/lib/rules/vars-on-top.js
+++ b/tools/eslint/lib/rules/vars-on-top.js
@@ -47,6 +47,23 @@ module.exports = {
}
/**
+ * Checks whether a given node is a variable declaration or not.
+ *
+ * @param {ASTNode} node - any node
+ * @returns {boolean} `true` if the node is a variable declaration.
+ */
+ function isVariableDeclaration(node) {
+ return (
+ node.type === "VariableDeclaration" ||
+ (
+ node.type === "ExportNamedDeclaration" &&
+ node.declaration &&
+ node.declaration.type === "VariableDeclaration"
+ )
+ );
+ }
+
+ /**
* Checks whether this variable is on top of the block body
* @param {ASTNode} node - The node to check
* @param {ASTNode[]} statements - collection of ASTNodes for the parent node block
@@ -64,9 +81,7 @@ module.exports = {
}
for (; i < l; ++i) {
- if (statements[i].type !== "VariableDeclaration" &&
- (statements[i].type !== "ExportNamedDeclaration" ||
- statements[i].declaration.type !== "VariableDeclaration")) {
+ if (!isVariableDeclaration(statements[i])) {
return false;
}
if (statements[i] === node) {
diff --git a/tools/eslint/lib/rules/wrap-iife.js b/tools/eslint/lib/rules/wrap-iife.js
index 1dd1a0c5af..2f73699a42 100644
--- a/tools/eslint/lib/rules/wrap-iife.js
+++ b/tools/eslint/lib/rules/wrap-iife.js
@@ -28,6 +28,8 @@ module.exports = {
var style = context.options[0] || "outside";
+ var sourceCode = context.getSourceCode();
+
/**
* Check if the node is wrapped in ()
* @param {ASTNode} node node to evaluate
@@ -35,8 +37,8 @@ module.exports = {
* @private
*/
function wrapped(node) {
- var previousToken = context.getTokenBefore(node),
- nextToken = context.getTokenAfter(node);
+ var previousToken = sourceCode.getTokenBefore(node),
+ nextToken = sourceCode.getTokenAfter(node);
return previousToken && previousToken.value === "(" &&
nextToken && nextToken.value === ")";
diff --git a/tools/eslint/lib/rules/wrap-regex.js b/tools/eslint/lib/rules/wrap-regex.js
index 96df3304c8..1aed713bdd 100644
--- a/tools/eslint/lib/rules/wrap-regex.js
+++ b/tools/eslint/lib/rules/wrap-regex.js
@@ -21,18 +21,19 @@ module.exports = {
},
create: function(context) {
+ var sourceCode = context.getSourceCode();
return {
Literal: function(node) {
- var token = context.getFirstToken(node),
+ var token = sourceCode.getFirstToken(node),
nodeType = token.type,
source,
grandparent,
ancestors;
if (nodeType === "RegularExpression") {
- source = context.getTokenBefore(node);
+ source = sourceCode.getTokenBefore(node);
ancestors = context.getAncestors();
grandparent = ancestors[ancestors.length - 1];
diff --git a/tools/eslint/lib/rules/yoda.js b/tools/eslint/lib/rules/yoda.js
index ce2709ec9a..0373e91a4a 100644
--- a/tools/eslint/lib/rules/yoda.js
+++ b/tools/eslint/lib/rules/yoda.js
@@ -151,6 +151,8 @@ module.exports = {
var exceptRange = (context.options[1] && context.options[1].exceptRange);
var onlyEquality = (context.options[1] && context.options[1].onlyEquality);
+ var sourceCode = context.getSourceCode();
+
/**
* Determines whether node represents a range test.
* A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
@@ -202,9 +204,9 @@ module.exports = {
function isParenWrapped() {
var tokenBefore, tokenAfter;
- return ((tokenBefore = context.getTokenBefore(node)) &&
+ return ((tokenBefore = sourceCode.getTokenBefore(node)) &&
tokenBefore.value === "(" &&
- (tokenAfter = context.getTokenAfter(node)) &&
+ (tokenAfter = sourceCode.getTokenAfter(node)) &&
tokenAfter.value === ")");
}
diff --git a/tools/eslint/lib/testers/rule-tester.js b/tools/eslint/lib/testers/rule-tester.js
index 4485e0d5fc..2ee87eca6d 100644
--- a/tools/eslint/lib/testers/rule-tester.js
+++ b/tools/eslint/lib/testers/rule-tester.js
@@ -331,6 +331,21 @@ RuleTester.prototype = {
}
/**
+ * Check if the AST was changed
+ * @param {ASTNode} beforeAST AST node before running
+ * @param {ASTNode} afterAST AST node after running
+ * @returns {void}
+ * @private
+ */
+ function assertASTDidntChange(beforeAST, afterAST) {
+ if (!lodash.isEqual(beforeAST, afterAST)) {
+
+ // Not using directly to avoid performance problem in node 6.1.0. See #6111
+ assert.deepEqual(beforeAST, afterAST, "Rule should not modify AST.");
+ }
+ }
+
+ /**
* Check if the template is valid or not
* all valid cases go through this
* @param {string} ruleName name of the rule
@@ -345,11 +360,7 @@ RuleTester.prototype = {
assert.equal(messages.length, 0, util.format("Should have no errors but had %d: %s",
messages.length, util.inspect(messages)));
- assert.deepEqual(
- result.beforeAST,
- result.afterAST,
- "Rule should not modify AST."
- );
+ assertASTDidntChange(result.beforeAST, result.afterAST);
}
/**
@@ -422,11 +433,7 @@ RuleTester.prototype = {
}
- assert.deepEqual(
- result.beforeAST,
- result.afterAST,
- "Rule should not modify AST."
- );
+ assertASTDidntChange(result.beforeAST, result.afterAST);
}
/*
diff --git a/tools/eslint/lib/util/glob-util.js b/tools/eslint/lib/util/glob-util.js
index dadefbd966..1209dabd6a 100644
--- a/tools/eslint/lib/util/glob-util.js
+++ b/tools/eslint/lib/util/glob-util.js
@@ -124,10 +124,12 @@ function listFilesToProcess(globPatterns, options) {
var ignored = false;
var isSilentlyIgnored;
+ if (ignoredPaths.contains(filename, "default")) {
+ ignored = (options.ignore !== false) && shouldWarnIgnored;
+ isSilentlyIgnored = !shouldWarnIgnored;
+ }
+
if (options.ignore !== false) {
- if (ignoredPaths.contains(filename, "default")) {
- isSilentlyIgnored = true;
- }
if (ignoredPaths.contains(filename, "custom")) {
if (shouldWarnIgnored) {
ignored = true;
@@ -135,10 +137,12 @@ function listFilesToProcess(globPatterns, options) {
isSilentlyIgnored = true;
}
}
- if (isSilentlyIgnored && !ignored) {
- return;
- }
}
+
+ if (isSilentlyIgnored && !ignored) {
+ return;
+ }
+
if (added[filename]) {
return;
}
@@ -150,7 +154,8 @@ function listFilesToProcess(globPatterns, options) {
ignoredPaths = new IgnoredPaths(options);
globOptions = {
nodir: true,
- cwd: cwd
+ cwd: cwd,
+ ignore: ignoredPaths.getIgnoredFoldersGlobPatterns()
};
debug("Creating list of files to process.");
diff --git a/tools/eslint/lib/util/npm-util.js b/tools/eslint/lib/util/npm-util.js
index fd081307fd..9f28dc2b7e 100644
--- a/tools/eslint/lib/util/npm-util.js
+++ b/tools/eslint/lib/util/npm-util.js
@@ -11,7 +11,8 @@
var fs = require("fs"),
path = require("path"),
- shell = require("shelljs");
+ shell = require("shelljs"),
+ log = require("../logging");
//------------------------------------------------------------------------------
// Helpers
@@ -69,11 +70,18 @@ function installSyncSaveDev(packages) {
function check(packages, opt) {
var deps = [];
var pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson();
+ var fileJson;
if (!pkgJson) {
throw new Error("Could not find a package.json file. Run 'npm init' to create one.");
}
- var fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
+
+ try {
+ fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
+ } catch (e) {
+ log.info("Could not read package.json file. Please check that the file contains valid JSON.");
+ throw new Error(e);
+ }
if (opt.devDependencies && typeof fileJson.devDependencies === "object") {
deps = deps.concat(Object.keys(fileJson.devDependencies));
@@ -116,6 +124,16 @@ function checkDevDeps(packages) {
return check(packages, {devDependencies: true});
}
+/**
+ * Check whether package.json is found in current path.
+ *
+ * @param {string=} startDir Starting directory
+ * @returns {boolean} Whether a package.json is found in current path.
+ */
+function checkPackageJson(startDir) {
+ return !!findPackageJson(startDir);
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@@ -123,5 +141,6 @@ function checkDevDeps(packages) {
module.exports = {
installSyncSaveDev: installSyncSaveDev,
checkDeps: checkDeps,
- checkDevDeps: checkDevDeps
+ checkDevDeps: checkDevDeps,
+ checkPackageJson: checkPackageJson
};
diff --git a/tools/eslint/lib/util/path-util.js b/tools/eslint/lib/util/path-util.js
index ddc0b60625..a199046bb7 100644
--- a/tools/eslint/lib/util/path-util.js
+++ b/tools/eslint/lib/util/path-util.js
@@ -8,8 +8,7 @@
// Requirements
//------------------------------------------------------------------------------
-var path = require("path"),
- isAbsolute = require("path-is-absolute");
+var path = require("path");
//------------------------------------------------------------------------------
// Private
@@ -51,11 +50,11 @@ function convertPathToPosix(filepath) {
function getRelativePath(filepath, baseDir) {
var relativePath;
- if (!isAbsolute(filepath)) {
+ if (!path.isAbsolute(filepath)) {
filepath = path.resolve(filepath);
}
if (baseDir) {
- if (!isAbsolute(baseDir)) {
+ if (!path.isAbsolute(baseDir)) {
throw new Error("baseDir should be an absolute path");
}
relativePath = path.relative(baseDir, filepath);
diff --git a/tools/eslint/lib/util/source-code-fixer.js b/tools/eslint/lib/util/source-code-fixer.js
index e8c440d7c2..042eff591f 100644
--- a/tools/eslint/lib/util/source-code-fixer.js
+++ b/tools/eslint/lib/util/source-code-fixer.js
@@ -85,11 +85,7 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) {
// sort in reverse order of occurrence
fixes.sort(function(a, b) {
- if (a.fix.range[1] <= b.fix.range[0]) {
- return 1;
- } else {
- return -1;
- }
+ return b.fix.range[1] - a.fix.range[1] || b.fix.range[0] - a.fix.range[0];
});
// split into array of characters for easier manipulation