Browse Source

tools: update ESLint to v3.8.0

Update ESLint to v3.8.0.

* Installed with `npm install --production` to avoid installing
  unnecessary dev files
* Used `dmn -f clean` to further eliminate unneeded files

PR-URL: https://github.com/nodejs/node/pull/9112
Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
v6.x
Rich Trott 8 years ago
committed by Myles Borins
parent
commit
e9d5cd79bb
  1. 3412
      tools/eslint/CHANGELOG.md
  2. 1
      tools/eslint/README.md
  3. 26
      tools/eslint/bin/eslint.js
  4. 1
      tools/eslint/conf/eslint.json
  5. 23
      tools/eslint/lib/ast-utils.js
  6. 51
      tools/eslint/lib/cli-engine.js
  7. 37
      tools/eslint/lib/cli.js
  8. 12
      tools/eslint/lib/code-path-analysis/code-path-analyzer.js
  9. 2
      tools/eslint/lib/code-path-analysis/code-path-state.js
  10. 2
      tools/eslint/lib/code-path-analysis/code-path.js
  11. 34
      tools/eslint/lib/code-path-analysis/debug-helpers.js
  12. 4
      tools/eslint/lib/code-path-analysis/fork-context.js
  13. 10
      tools/eslint/lib/config.js
  14. 2
      tools/eslint/lib/config/autoconfig.js
  15. 58
      tools/eslint/lib/config/config-file.js
  16. 18
      tools/eslint/lib/config/config-initializer.js
  17. 2
      tools/eslint/lib/config/config-ops.js
  18. 2
      tools/eslint/lib/config/environments.js
  19. 6
      tools/eslint/lib/config/plugins.js
  20. 38
      tools/eslint/lib/eslint.js
  21. 15
      tools/eslint/lib/formatters/checkstyle.js
  22. 14
      tools/eslint/lib/formatters/compact.js
  23. 6
      tools/eslint/lib/formatters/html.js
  24. 13
      tools/eslint/lib/formatters/jslint-xml.js
  25. 16
      tools/eslint/lib/formatters/junit.js
  26. 10
      tools/eslint/lib/formatters/stylish.js
  27. 6
      tools/eslint/lib/formatters/table.js
  28. 8
      tools/eslint/lib/formatters/tap.js
  29. 13
      tools/eslint/lib/formatters/unix.js
  30. 12
      tools/eslint/lib/formatters/visualstudio.js
  31. 8
      tools/eslint/lib/ignored-paths.js
  32. 3
      tools/eslint/lib/internal-rules/.eslintrc.yml
  33. 131
      tools/eslint/lib/internal-rules/internal-consistent-docs-description.js
  34. 4
      tools/eslint/lib/options.js
  35. 2
      tools/eslint/lib/rules.js
  36. 1
      tools/eslint/lib/rules/.eslintrc.yml
  37. 4
      tools/eslint/lib/rules/array-callback-return.js
  38. 2
      tools/eslint/lib/rules/arrow-parens.js
  39. 28
      tools/eslint/lib/rules/class-methods-use-this.js
  40. 244
      tools/eslint/lib/rules/comma-dangle.js
  41. 97
      tools/eslint/lib/rules/curly.js
  42. 22
      tools/eslint/lib/rules/dot-location.js
  43. 66
      tools/eslint/lib/rules/eol-last.js
  44. 2
      tools/eslint/lib/rules/func-call-spacing.js
  45. 149
      tools/eslint/lib/rules/func-name-matching.js
  46. 4
      tools/eslint/lib/rules/id-length.js
  47. 216
      tools/eslint/lib/rules/indent.js
  48. 2
      tools/eslint/lib/rules/init-declarations.js
  49. 44
      tools/eslint/lib/rules/keyword-spacing.js
  50. 2
      tools/eslint/lib/rules/line-comment-position.js
  51. 9
      tools/eslint/lib/rules/lines-around-directive.js
  52. 7
      tools/eslint/lib/rules/max-len.js
  53. 2
      tools/eslint/lib/rules/max-params.js
  54. 20
      tools/eslint/lib/rules/max-statements-per-line.js
  55. 2
      tools/eslint/lib/rules/max-statements.js
  56. 2
      tools/eslint/lib/rules/new-cap.js
  57. 2
      tools/eslint/lib/rules/newline-after-var.js
  58. 13
      tools/eslint/lib/rules/no-extra-bind.js
  59. 17
      tools/eslint/lib/rules/no-extra-parens.js
  60. 35
      tools/eslint/lib/rules/no-implicit-coercion.js
  61. 2
      tools/eslint/lib/rules/no-implicit-globals.js
  62. 2
      tools/eslint/lib/rules/no-inner-declarations.js
  63. 46
      tools/eslint/lib/rules/no-lonely-if.js
  64. 2
      tools/eslint/lib/rules/no-mixed-requires.js
  65. 180
      tools/eslint/lib/rules/no-multiple-empty-lines.js
  66. 2
      tools/eslint/lib/rules/no-redeclare.js
  67. 30
      tools/eslint/lib/rules/no-regex-spaces.js
  68. 149
      tools/eslint/lib/rules/no-restricted-properties.js
  69. 2
      tools/eslint/lib/rules/no-shadow.js
  70. 2
      tools/eslint/lib/rules/no-spaced-func.js
  71. 33
      tools/eslint/lib/rules/no-undef-init.js
  72. 2
      tools/eslint/lib/rules/no-unused-expressions.js
  73. 38
      tools/eslint/lib/rules/no-unused-vars.js
  74. 23
      tools/eslint/lib/rules/no-useless-computed-key.js
  75. 53
      tools/eslint/lib/rules/no-useless-escape.js
  76. 6
      tools/eslint/lib/rules/no-whitespace-before-property.js
  77. 37
      tools/eslint/lib/rules/object-shorthand.js
  78. 9
      tools/eslint/lib/rules/one-var-declaration-per-line.js
  79. 5
      tools/eslint/lib/rules/prefer-arrow-callback.js
  80. 21
      tools/eslint/lib/rules/prefer-numeric-literals.js
  81. 23
      tools/eslint/lib/rules/prefer-spread.js
  82. 130
      tools/eslint/lib/rules/prefer-template.js
  83. 106
      tools/eslint/lib/rules/quote-props.js
  84. 24
      tools/eslint/lib/rules/quotes.js
  85. 2
      tools/eslint/lib/rules/semi.js
  86. 4
      tools/eslint/lib/rules/sort-keys.js
  87. 40
      tools/eslint/lib/rules/space-before-function-paren.js
  88. 2
      tools/eslint/lib/rules/space-infix-ops.js
  89. 14
      tools/eslint/lib/rules/space-unary-ops.js
  90. 57
      tools/eslint/lib/rules/strict.js
  91. 6
      tools/eslint/lib/rules/valid-jsdoc.js
  92. 21
      tools/eslint/lib/rules/valid-typeof.js
  93. 111
      tools/eslint/lib/rules/wrap-iife.js
  94. 67
      tools/eslint/lib/rules/yoda.js
  95. 68
      tools/eslint/lib/testers/rule-tester.js
  96. 2
      tools/eslint/lib/timing.js
  97. 4
      tools/eslint/lib/util/glob-util.js
  98. 2
      tools/eslint/lib/util/module-resolver.js
  99. 2
      tools/eslint/lib/util/node-event-generator.js
  100. 2
      tools/eslint/lib/util/npm-util.js

3412
tools/eslint/CHANGELOG.md

File diff suppressed because it is too large

1
tools/eslint/README.md

@ -129,6 +129,7 @@ These folks keep the project moving and are resources for help.
* Kevin Partington ([@platinumazure](https://github.com/platinumazure)) * Kevin Partington ([@platinumazure](https://github.com/platinumazure))
* Vitor Balocco ([@vitorbal](https://github.com/vitorbal)) * Vitor Balocco ([@vitorbal](https://github.com/vitorbal))
* James Henry ([@JamesHenry](https://github.com/JamesHenry)) * James Henry ([@JamesHenry](https://github.com/JamesHenry))
* Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
## Releases ## Releases

26
tools/eslint/bin/eslint.js

@ -5,13 +5,15 @@
* @author Nicholas C. Zakas * @author Nicholas C. Zakas
*/ */
/* eslint no-console:off, no-process-exit:off */
"use strict"; "use strict";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helpers // Helpers
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var useStdIn = (process.argv.indexOf("--stdin") > -1), const useStdIn = (process.argv.indexOf("--stdin") > -1),
init = (process.argv.indexOf("--init") > -1), init = (process.argv.indexOf("--init") > -1),
debug = (process.argv.indexOf("--debug") > -1); debug = (process.argv.indexOf("--debug") > -1);
@ -25,7 +27,7 @@ if (debug) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// now we can safely include the other modules that use debug // now we can safely include the other modules that use debug
var concat = require("concat-stream"), const concat = require("concat-stream"),
cli = require("../lib/cli"), cli = require("../lib/cli"),
path = require("path"), path = require("path"),
fs = require("fs"); fs = require("fs");
@ -34,15 +36,16 @@ var concat = require("concat-stream"),
// Execution // Execution
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
process.on("uncaughtException", function(err){ process.on("uncaughtException", function(err) {
// lazy load // lazy load
var lodash = require("lodash"); const lodash = require("lodash");
if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) { if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
var template = lodash.template(fs.readFileSync(path.resolve(__dirname, "../messages/" + err.messageTemplate + ".txt"), "utf-8")); const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8"));
console.log("\nOops! Something went wrong! :("); console.log("\nOops! Something went wrong! :(");
console.log("\n" + template(err.messageData || {})); console.log(`\n${template(err.messageData || {})}`);
} else { } else {
console.log(err.message); console.log(err.message);
console.log(err.stack); console.log(err.stack);
@ -53,16 +56,11 @@ process.on("uncaughtException", function(err){
if (useStdIn) { if (useStdIn) {
process.stdin.pipe(concat({ encoding: "string" }, function(text) { process.stdin.pipe(concat({ encoding: "string" }, function(text) {
try { process.exitCode = cli.execute(process.argv, text);
process.exitCode = cli.execute(process.argv, text);
} catch (ex) {
console.error(ex.message);
console.error(ex.stack);
process.exitCode = 1;
}
})); }));
} else if (init) { } else if (init) {
var configInit = require("../lib/config/config-initializer"); const configInit = require("../lib/config/config-initializer");
configInit.initializeConfig(function(err) { configInit.initializeConfig(function(err) {
if (err) { if (err) {
process.exitCode = 1; process.exitCode = 1;

1
tools/eslint/conf/eslint.json

@ -158,6 +158,7 @@
"eqeqeq": "off", "eqeqeq": "off",
"func-call-spacing": "off", "func-call-spacing": "off",
"func-names": "off", "func-names": "off",
"func-name-matching": "off",
"func-style": "off", "func-style": "off",
"generator-star-spacing": "off", "generator-star-spacing": "off",
"global-require": "off", "global-require": "off",

23
tools/eslint/lib/ast-utils.js

@ -560,6 +560,7 @@ module.exports = {
/* falls through */ /* falls through */
case "UnaryExpression": case "UnaryExpression":
case "AwaitExpression":
return 14; return 14;
case "UpdateExpression": case "UpdateExpression":
@ -715,5 +716,27 @@ module.exports = {
} }
return directives; return directives;
},
/**
* Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added
after the node will be parsed as a decimal point, rather than a property-access dot.
* @param {ASTNode} node - The node to check.
* @returns {boolean} `true` if this node is a decimal integer.
* @example
*
* 5 // true
* 5. // false
* 5.0 // false
* 05 // false
* 0x5 // false
* 0b101 // false
* 0o5 // false
* 5e0 // false
* '5' // false
*/
isDecimalInteger(node) {
return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/.test(node.raw);
} }
}; };

51
tools/eslint/lib/cli-engine.js

@ -75,6 +75,8 @@ const debug = require("debug")("eslint:cli-engine");
* @property {LintMessage[]} messages All of the messages for the result. * @property {LintMessage[]} messages All of the messages for the result.
* @property {number} errorCount Number or errors for the result. * @property {number} errorCount Number or errors for the result.
* @property {number} warningCount Number or warnings for the result. * @property {number} warningCount Number or warnings for the result.
* @property {string=} [source] The source code of the file that was linted.
* @property {string=} [output] The source code of the file that was linted, with as many fixes applied as possible.
*/ */
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -150,10 +152,10 @@ function multipassFix(text, config, options) {
do { do {
passNumber++; passNumber++;
debug("Linting code for " + options.filename + " (pass " + passNumber + ")"); debug(`Linting code for ${options.filename} (pass ${passNumber})`);
messages = eslint.verify(text, config, options); messages = eslint.verify(text, config, options);
debug("Generating fixed text for " + options.filename + " (pass " + passNumber + ")"); debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages); fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
// stop if there are any syntax errors. // stop if there are any syntax errors.
@ -175,7 +177,7 @@ function multipassFix(text, config, options) {
/* /*
* If the last result had fixes, we need to lint again to me sure we have * If the last result had fixes, we need to lint again to be sure we have
* the most up-to-date information. * the most up-to-date information.
*/ */
if (fixedResult.fixed) { if (fixedResult.fixed) {
@ -198,7 +200,7 @@ function multipassFix(text, config, options) {
* @param {string} filename An optional string representing the texts filename. * @param {string} filename An optional string representing the texts filename.
* @param {boolean} fix Indicates if fixes should be processed. * @param {boolean} fix Indicates if fixes should be processed.
* @param {boolean} allowInlineConfig Allow/ignore comments that change config. * @param {boolean} allowInlineConfig Allow/ignore comments that change config.
* @returns {Result} The results for linting on this text. * @returns {LintResult} The results for linting on this text.
* @private * @private
*/ */
function processText(text, configHelper, filename, fix, allowInlineConfig) { function processText(text, configHelper, filename, fix, allowInlineConfig) {
@ -218,7 +220,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
} }
filename = filename || "<text>"; filename = filename || "<text>";
debug("Linting " + filename); debug(`Linting ${filename}`);
const config = configHelper.getConfig(filePath); const config = configHelper.getConfig(filePath);
if (config.plugins) { if (config.plugins) {
@ -279,6 +281,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
result.output = fixedResult.output; result.output = fixedResult.output;
} }
if (result.errorCount + result.warningCount > 0 && typeof result.output === "undefined") {
result.source = text;
}
return result; return result;
} }
@ -288,7 +294,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
* @param {string} filename The filename of the file being checked. * @param {string} filename The filename of the file being checked.
* @param {Object} configHelper The configuration options for ESLint. * @param {Object} configHelper The configuration options for ESLint.
* @param {Object} options The CLIEngine options object. * @param {Object} options The CLIEngine options object.
* @returns {Result} The results for linting on this file. * @returns {LintResult} The results for linting on this file.
* @private * @private
*/ */
function processFile(filename, configHelper, options) { function processFile(filename, configHelper, options) {
@ -304,7 +310,7 @@ function processFile(filename, configHelper, options) {
* Returns result with warning by ignore settings * Returns result with warning by ignore settings
* @param {string} filePath - File path of checked code * @param {string} filePath - File path of checked code
* @param {string} baseDir - Absolute path of base directory * @param {string} baseDir - Absolute path of base directory
* @returns {Result} Result with single warning * @returns {LintResult} Result with single warning
* @private * @private
*/ */
function createIgnoreResult(filePath, baseDir) { function createIgnoreResult(filePath, baseDir) {
@ -376,7 +382,7 @@ function getCacheFile(cacheFile, cwd) {
* @returns {string} the resolved path to the cacheFile * @returns {string} the resolved path to the cacheFile
*/ */
function getCacheFileForDirectory() { function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, ".cache_" + hash(cwd)); return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
} }
let fileStats; let fileStats;
@ -461,7 +467,7 @@ function CLIEngine(options) {
const cwd = this.options.cwd; const cwd = this.options.cwd;
this.options.rulePaths.forEach(function(rulesdir) { this.options.rulePaths.forEach(function(rulesdir) {
debug("Loading rules from " + rulesdir); debug(`Loading rules from ${rulesdir}`);
rules.load(rulesdir, cwd); rules.load(rulesdir, cwd);
}); });
} }
@ -497,13 +503,13 @@ CLIEngine.getFormatter = function(format) {
formatterPath = path.resolve(cwd, format); formatterPath = path.resolve(cwd, format);
} else { } else {
formatterPath = "./formatters/" + format; formatterPath = `./formatters/${format}`;
} }
try { try {
return require(formatterPath); return require(formatterPath);
} catch (ex) { } catch (ex) {
ex.message = "There was a problem loading formatter: " + formatterPath + "\nError: " + ex.message; ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
throw ex; throw ex;
} }
@ -524,12 +530,13 @@ CLIEngine.getErrorResults = function(results) {
const filteredMessages = result.messages.filter(isErrorMessage); const filteredMessages = result.messages.filter(isErrorMessage);
if (filteredMessages.length > 0) { if (filteredMessages.length > 0) {
filtered.push({ filtered.push(
filePath: result.filePath, Object.assign(result, {
messages: filteredMessages, messages: filteredMessages,
errorCount: filteredMessages.length, errorCount: filteredMessages.length,
warningCount: 0 warningCount: 0
}); })
);
} }
}); });
@ -608,7 +615,7 @@ CLIEngine.prototype = {
const eslintVersion = pkg.version; const eslintVersion = pkg.version;
prevConfig.hash = hash(eslintVersion + "_" + stringify(config)); prevConfig.hash = hash(`${eslintVersion}_${stringify(config)}`);
} }
return prevConfig.hash; return prevConfig.hash;
@ -645,7 +652,7 @@ CLIEngine.prototype = {
const changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig; const changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
if (!changed) { if (!changed) {
debug("Skipping file since hasn't changed: " + filename); debug(`Skipping file since hasn't changed: ${filename}`);
/* /*
* Add the the cached results (always will be 0 error and * Add the the cached results (always will be 0 error and
@ -662,7 +669,7 @@ CLIEngine.prototype = {
fileCache.destroy(); fileCache.destroy();
} }
debug("Processing " + filename); debug(`Processing ${filename}`);
const res = processFile(filename, configHelper, options); const res = processFile(filename, configHelper, options);
@ -674,7 +681,7 @@ CLIEngine.prototype = {
* next execution will also operate on this file * next execution will also operate on this file
*/ */
if (res.errorCount > 0 || res.warningCount > 0) { if (res.errorCount > 0 || res.warningCount > 0) {
debug("File has problems, skipping it: " + filename); debug(`File has problems, skipping it: ${filename}`);
// remove the entry from the cache // remove the entry from the cache
fileCache.removeEntry(filename); fileCache.removeEntry(filename);
@ -713,7 +720,7 @@ CLIEngine.prototype = {
fileCache.reconcile(); fileCache.reconcile();
} }
debug("Linting complete in: " + (Date.now() - startTime) + "ms"); debug(`Linting complete in: ${Date.now() - startTime}ms`);
return { return {
results, results,

37
tools/eslint/lib/cli.js

@ -135,15 +135,30 @@ const cli = {
if (currentOptions.version) { // version from package.json if (currentOptions.version) { // version from package.json
log.info("v" + require("../package.json").version); log.info(`v${require("../package.json").version}`);
} else if (currentOptions.printConfig) {
if (files.length) {
log.error("The --print-config option must be used with exactly one file name.");
return 1;
} else if (text) {
log.error("The --print-config option is not available for piped-in code.");
return 1;
}
const engine = new CLIEngine(translateOptions(currentOptions));
const fileConfig = engine.getConfigForFile(currentOptions.printConfig);
log.info(JSON.stringify(fileConfig, null, " "));
return 0;
} else if (currentOptions.help || (!files.length && !text)) { } else if (currentOptions.help || (!files.length && !text)) {
log.info(options.generateHelp()); log.info(options.generateHelp());
} else { } else {
debug("Running on " + (text ? "text" : "files")); debug(`Running on ${text ? "text" : "files"}`);
// disable --fix for piped-in code until we know how to do it correctly // disable --fix for piped-in code until we know how to do it correctly
if (text && currentOptions.fix) { if (text && currentOptions.fix) {
@ -153,24 +168,6 @@ const cli = {
const engine = new CLIEngine(translateOptions(currentOptions)); const engine = new CLIEngine(translateOptions(currentOptions));
if (currentOptions.printConfig) {
if (files.length !== 1) {
log.error("The --print-config option requires a " +
"single file as positional argument.");
return 1;
}
if (text) {
log.error("The --print-config option is not available for piped-in code.");
return 1;
}
const fileConfig = engine.getConfigForFile(files[0]);
log.info(JSON.stringify(fileConfig, null, " "));
return 0;
}
const report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files); const report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
if (currentOptions.fix) { if (currentOptions.fix) {

12
tools/eslint/lib/code-path-analysis/code-path-analyzer.js

@ -148,7 +148,7 @@ function forwardCurrentToHead(analyzer, node) {
headSegment = headSegments[i]; headSegment = headSegments[i];
if (currentSegment !== headSegment && currentSegment) { if (currentSegment !== headSegment && currentSegment) {
debug.dump("onCodePathSegmentEnd " + currentSegment.id); debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
if (currentSegment.reachable) { if (currentSegment.reachable) {
analyzer.emitter.emit( analyzer.emitter.emit(
@ -168,7 +168,7 @@ function forwardCurrentToHead(analyzer, node) {
headSegment = headSegments[i]; headSegment = headSegments[i];
if (currentSegment !== headSegment && headSegment) { if (currentSegment !== headSegment && headSegment) {
debug.dump("onCodePathSegmentStart " + headSegment.id); debug.dump(`onCodePathSegmentStart ${headSegment.id}`);
CodePathSegment.markUsed(headSegment); CodePathSegment.markUsed(headSegment);
if (headSegment.reachable) { if (headSegment.reachable) {
@ -197,7 +197,7 @@ function leaveFromCurrentSegment(analyzer, node) {
for (let i = 0; i < currentSegments.length; ++i) { for (let i = 0; i < currentSegments.length; ++i) {
const currentSegment = currentSegments[i]; const currentSegment = currentSegments[i];
debug.dump("onCodePathSegmentEnd " + currentSegment.id); debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
if (currentSegment.reachable) { if (currentSegment.reachable) {
analyzer.emitter.emit( analyzer.emitter.emit(
"onCodePathSegmentEnd", "onCodePathSegmentEnd",
@ -353,7 +353,7 @@ function processCodePathToEnter(analyzer, node) {
state = CodePath.getState(codePath); state = CodePath.getState(codePath);
// Emits onCodePathStart events. // Emits onCodePathStart events.
debug.dump("onCodePathStart " + codePath.id); debug.dump(`onCodePathStart ${codePath.id}`);
analyzer.emitter.emit("onCodePathStart", codePath, node); analyzer.emitter.emit("onCodePathStart", codePath, node);
break; break;
@ -546,7 +546,7 @@ function postprocess(analyzer, node) {
leaveFromCurrentSegment(analyzer, node); leaveFromCurrentSegment(analyzer, node);
// Emits onCodePathEnd event of this code path. // Emits onCodePathEnd event of this code path.
debug.dump("onCodePathEnd " + codePath.id); debug.dump(`onCodePathEnd ${codePath.id}`);
analyzer.emitter.emit("onCodePathEnd", codePath, node); analyzer.emitter.emit("onCodePathEnd", codePath, node);
debug.dumpDot(codePath); debug.dumpDot(codePath);
@ -643,7 +643,7 @@ CodePathAnalyzer.prototype = {
*/ */
onLooped(fromSegment, toSegment) { onLooped(fromSegment, toSegment) {
if (fromSegment.reachable && toSegment.reachable) { if (fromSegment.reachable && toSegment.reachable) {
debug.dump("onCodePathSegmentLoop " + fromSegment.id + " -> " + toSegment.id); debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`);
this.emitter.emit( this.emitter.emit(
"onCodePathSegmentLoop", "onCodePathSegmentLoop",
fromSegment, fromSegment,

2
tools/eslint/lib/code-path-analysis/code-path-state.js

@ -967,7 +967,7 @@ CodePathState.prototype = {
/* istanbul ignore next */ /* istanbul ignore next */
default: default:
throw new Error("unknown type: \"" + type + "\""); throw new Error(`unknown type: "${type}"`);
} }
}, },

2
tools/eslint/lib/code-path-analysis/code-path.js

@ -49,7 +49,7 @@ function CodePath(id, upper, onLooped) {
Object.defineProperty( Object.defineProperty(
this, this,
"internal", "internal",
{value: new CodePathState(new IdGenerator(id + "_"), onLooped)}); {value: new CodePathState(new IdGenerator(`${id}_`), onLooped)});
// Adds this into `childCodePaths` of `upper`. // Adds this into `childCodePaths` of `upper`.
if (upper) { if (upper) {

34
tools/eslint/lib/code-path-analysis/debug-helpers.js

@ -64,10 +64,10 @@ module.exports = {
} }
} }
debug( debug([
state.currentSegments.map(getId).join(",") + ") " + `${state.currentSegments.map(getId).join(",")})`,
node.type + (leaving ? ":exit" : "") `${node.type}${leaving ? ":exit" : ""}`
); ].join(" "));
}, },
/** /**
@ -99,7 +99,7 @@ module.exports = {
for (const id in traceMap) { // eslint-disable-line guard-for-in for (const id in traceMap) { // eslint-disable-line guard-for-in
const segment = traceMap[id]; const segment = traceMap[id];
text += id + "["; text += `${id}[`;
if (segment.reachable) { if (segment.reachable) {
text += "label=\""; text += "label=\"";
@ -110,17 +110,17 @@ module.exports = {
if (segment.internal.nodes.length > 0) { if (segment.internal.nodes.length > 0) {
text += segment.internal.nodes.map(function(node) { text += segment.internal.nodes.map(function(node) {
switch (node.type) { switch (node.type) {
case "Identifier": return node.type + " (" + node.name + ")"; case "Identifier": return `${node.type} (${node.name})`;
case "Literal": return node.type + " (" + node.value + ")"; case "Literal": return `${node.type} (${node.value})`;
default: return node.type; default: return node.type;
} }
}).join("\\n"); }).join("\\n");
} else if (segment.internal.exitNodes.length > 0) { } else if (segment.internal.exitNodes.length > 0) {
text += segment.internal.exitNodes.map(function(node) { text += segment.internal.exitNodes.map(function(node) {
switch (node.type) { switch (node.type) {
case "Identifier": return node.type + ":exit (" + node.name + ")"; case "Identifier": return `${node.type}:exit (${node.name})`;
case "Literal": return node.type + ":exit (" + node.value + ")"; case "Literal": return `${node.type}:exit (${node.value})`;
default: return node.type + ":exit"; default: return `${node.type}:exit`;
} }
}).join("\\n"); }).join("\\n");
} else { } else {
@ -130,7 +130,7 @@ module.exports = {
text += "\"];\n"; text += "\"];\n";
} }
text += arrows + "\n"; text += `${arrows}\n`;
text += "}"; text += "}";
debug("DOT", text); debug("DOT", text);
}, },
@ -147,7 +147,7 @@ module.exports = {
const stack = [[codePath.initialSegment, 0]]; const stack = [[codePath.initialSegment, 0]];
const done = traceMap || Object.create(null); const done = traceMap || Object.create(null);
let lastId = codePath.initialSegment.id; let lastId = codePath.initialSegment.id;
let text = "initial->" + codePath.initialSegment.id; let text = `initial->${codePath.initialSegment.id}`;
while (stack.length > 0) { while (stack.length > 0) {
const item = stack.pop(); const item = stack.pop();
@ -166,9 +166,9 @@ module.exports = {
} }
if (lastId === segment.id) { if (lastId === segment.id) {
text += "->" + nextSegment.id; text += `->${nextSegment.id}`;
} else { } else {
text += ";\n" + segment.id + "->" + nextSegment.id; text += `;\n${segment.id}->${nextSegment.id}`;
} }
lastId = nextSegment.id; lastId = nextSegment.id;
@ -180,7 +180,7 @@ module.exports = {
if (lastId === finalSegment.id) { if (lastId === finalSegment.id) {
text += "->final"; text += "->final";
} else { } else {
text += ";\n" + finalSegment.id + "->final"; text += `;\n${finalSegment.id}->final`;
} }
lastId = null; lastId = null;
}); });
@ -189,11 +189,11 @@ module.exports = {
if (lastId === finalSegment.id) { if (lastId === finalSegment.id) {
text += "->thrown"; text += "->thrown";
} else { } else {
text += ";\n" + finalSegment.id + "->thrown"; text += `;\n${finalSegment.id}->thrown`;
} }
lastId = null; lastId = null;
}); });
return text + ";"; return `${text};`;
} }
}; };

4
tools/eslint/lib/code-path-analysis/fork-context.js

@ -187,7 +187,7 @@ ForkContext.prototype = {
* @returns {void} * @returns {void}
*/ */
add(segments) { add(segments) {
assert(segments.length >= this.count, segments.length + " >= " + this.count); assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
this.segmentsList.push(mergeExtraSegments(this, segments)); this.segmentsList.push(mergeExtraSegments(this, segments));
}, },
@ -200,7 +200,7 @@ ForkContext.prototype = {
* @returns {void} * @returns {void}
*/ */
replaceHead(segments) { replaceHead(segments) {
assert(segments.length >= this.count, segments.length + " >= " + this.count); assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
}, },

10
tools/eslint/lib/config.js

@ -126,7 +126,7 @@ function getLocalConfig(thisConfig, directory) {
continue; continue;
} }
debug("Loading " + localConfigFile); debug(`Loading ${localConfigFile}`);
const localConfig = loadConfig(localConfigFile); const localConfig = loadConfig(localConfigFile);
// Don't consider a local config file found if the config is null // Don't consider a local config file found if the config is null
@ -140,7 +140,7 @@ function getLocalConfig(thisConfig, directory) {
} }
found = true; found = true;
debug("Using " + localConfigFile); debug(`Using ${localConfigFile}`);
config = ConfigOps.merge(localConfig, config); config = ConfigOps.merge(localConfig, config);
} }
@ -221,8 +221,8 @@ function Config(options) {
this.options = options; this.options = options;
if (useConfig) { if (useConfig) {
debug("Using command line config " + useConfig); debug(`Using command line config ${useConfig}`);
if (isResolvable(useConfig) || isResolvable("eslint-config-" + useConfig) || useConfig.charAt(0) === "@") { if (isResolvable(useConfig) || isResolvable(`eslint-config-${useConfig}`) || useConfig.charAt(0) === "@") {
this.useSpecificConfig = loadConfig(useConfig); this.useSpecificConfig = loadConfig(useConfig);
} else { } else {
this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, useConfig)); this.useSpecificConfig = loadConfig(path.resolve(this.options.cwd, useConfig));
@ -241,7 +241,7 @@ Config.prototype.getConfig = function(filePath) {
let config, let config,
userConfig; userConfig;
debug("Constructing config for " + (filePath ? filePath : "text")); debug(`Constructing config for ${filePath ? filePath : "text"}`);
config = this.cache[directory]; config = this.cache[directory];

2
tools/eslint/lib/config/autoconfig.js

@ -295,7 +295,7 @@ Registry.prototype = {
const totalFilesLinting = filenames.length * ruleSets.length; const totalFilesLinting = filenames.length * ruleSets.length;
filenames.forEach(function(filename) { filenames.forEach(function(filename) {
debug("Linting file: " + filename); debug(`Linting file: ${filename}`);
ruleSetIdx = 0; ruleSetIdx = 0;

58
tools/eslint/lib/config/config-file.js

@ -90,7 +90,7 @@ function isFilePath(filePath) {
* @private * @private
*/ */
function loadYAMLConfigFile(filePath) { function loadYAMLConfigFile(filePath) {
debug("Loading YAML config file: " + filePath); debug(`Loading YAML config file: ${filePath}`);
// lazy load YAML to improve performance when not used // lazy load YAML to improve performance when not used
const yaml = require("js-yaml"); const yaml = require("js-yaml");
@ -100,8 +100,8 @@ function loadYAMLConfigFile(filePath) {
// empty YAML file can be null, so always use // empty YAML file can be null, so always use
return yaml.safeLoad(readFile(filePath)) || {}; return yaml.safeLoad(readFile(filePath)) || {};
} catch (e) { } catch (e) {
debug("Error reading YAML file: " + filePath); debug(`Error reading YAML file: ${filePath}`);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
throw e; throw e;
} }
} }
@ -114,13 +114,13 @@ function loadYAMLConfigFile(filePath) {
* @private * @private
*/ */
function loadJSONConfigFile(filePath) { function loadJSONConfigFile(filePath) {
debug("Loading JSON config file: " + filePath); debug(`Loading JSON config file: ${filePath}`);
try { try {
return JSON.parse(stripComments(readFile(filePath))); return JSON.parse(stripComments(readFile(filePath)));
} catch (e) { } catch (e) {
debug("Error reading JSON file: " + filePath); debug(`Error reading JSON file: ${filePath}`);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
throw e; throw e;
} }
} }
@ -133,7 +133,7 @@ function loadJSONConfigFile(filePath) {
* @private * @private
*/ */
function loadLegacyConfigFile(filePath) { function loadLegacyConfigFile(filePath) {
debug("Loading config file: " + filePath); debug(`Loading config file: ${filePath}`);
// lazy load YAML to improve performance when not used // lazy load YAML to improve performance when not used
const yaml = require("js-yaml"); const yaml = require("js-yaml");
@ -141,8 +141,8 @@ function loadLegacyConfigFile(filePath) {
try { try {
return yaml.safeLoad(stripComments(readFile(filePath))) || /* istanbul ignore next */ {}; return yaml.safeLoad(stripComments(readFile(filePath))) || /* istanbul ignore next */ {};
} catch (e) { } catch (e) {
debug("Error reading YAML file: " + filePath); debug(`Error reading YAML file: ${filePath}`);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
throw e; throw e;
} }
} }
@ -155,12 +155,12 @@ function loadLegacyConfigFile(filePath) {
* @private * @private
*/ */
function loadJSConfigFile(filePath) { function loadJSConfigFile(filePath) {
debug("Loading JS config file: " + filePath); debug(`Loading JS config file: ${filePath}`);
try { try {
return requireUncached(filePath); return requireUncached(filePath);
} catch (e) { } catch (e) {
debug("Error reading JavaScript file: " + filePath); debug(`Error reading JavaScript file: ${filePath}`);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
throw e; throw e;
} }
} }
@ -173,12 +173,12 @@ function loadJSConfigFile(filePath) {
* @private * @private
*/ */
function loadPackageJSONConfigFile(filePath) { function loadPackageJSONConfigFile(filePath) {
debug("Loading package.json config file: " + filePath); debug(`Loading package.json config file: ${filePath}`);
try { try {
return loadJSONConfigFile(filePath).eslintConfig || null; return loadJSONConfigFile(filePath).eslintConfig || null;
} catch (e) { } catch (e) {
debug("Error reading package.json file: " + filePath); debug(`Error reading package.json file: ${filePath}`);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message; e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
throw e; throw e;
} }
} }
@ -233,7 +233,7 @@ function loadConfigFile(file) {
* @private * @private
*/ */
function writeJSONConfigFile(config, filePath) { function writeJSONConfigFile(config, filePath) {
debug("Writing JSON config file: " + filePath); debug(`Writing JSON config file: ${filePath}`);
const content = stringify(config, {cmp: sortByKey, space: 4}); const content = stringify(config, {cmp: sortByKey, space: 4});
@ -248,7 +248,7 @@ function writeJSONConfigFile(config, filePath) {
* @private * @private
*/ */
function writeYAMLConfigFile(config, filePath) { function writeYAMLConfigFile(config, filePath) {
debug("Writing YAML config file: " + filePath); debug(`Writing YAML config file: ${filePath}`);
// lazy load YAML to improve performance when not used // lazy load YAML to improve performance when not used
const yaml = require("js-yaml"); const yaml = require("js-yaml");
@ -266,9 +266,9 @@ function writeYAMLConfigFile(config, filePath) {
* @private * @private
*/ */
function writeJSConfigFile(config, filePath) { function writeJSConfigFile(config, filePath) {
debug("Writing JS config file: " + filePath); debug(`Writing JS config file: ${filePath}`);
const content = "module.exports = " + stringify(config, {cmp: sortByKey, space: 4}) + ";"; const content = `module.exports = ${stringify(config, {cmp: sortByKey, space: 4})};`;
fs.writeFileSync(filePath, content, "utf8"); fs.writeFileSync(filePath, content, "utf8");
} }
@ -387,7 +387,7 @@ function applyExtends(config, filePath, relativeTo) {
} }
try { try {
debug("Loading " + parentPath); debug(`Loading ${parentPath}`);
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue); return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
} catch (e) { } catch (e) {
@ -397,7 +397,7 @@ function applyExtends(config, filePath, relativeTo) {
* message so the user is able to see where it was referenced from, * message so the user is able to see where it was referenced from,
* then re-throw. * then re-throw.
*/ */
e.message += "\nReferenced from: " + filePath; e.message += `\nReferenced from: ${filePath}`;
throw e; throw e;
} }
@ -430,21 +430,21 @@ function normalizePackageName(name, prefix) {
* it's a scoped package * it's a scoped package
* package name is "eslint-config", or just a username * package name is "eslint-config", or just a username
*/ */
const scopedPackageShortcutRegex = new RegExp("^(@[^\/]+)(?:\/(?:" + prefix + ")?)?$"), const scopedPackageShortcutRegex = new RegExp(`^(@[^\/]+)(?:\/(?:${prefix})?)?$`),
scopedPackageNameRegex = new RegExp("^" + prefix + "(-|$)"); scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`);
if (scopedPackageShortcutRegex.test(name)) { if (scopedPackageShortcutRegex.test(name)) {
name = name.replace(scopedPackageShortcutRegex, "$1/" + prefix); name = name.replace(scopedPackageShortcutRegex, `$1/${prefix}`);
} else if (!scopedPackageNameRegex.test(name.split("/")[1])) { } else if (!scopedPackageNameRegex.test(name.split("/")[1])) {
/* /*
* for scoped packages, insert the eslint-config after the first / unless * for scoped packages, insert the eslint-config after the first / unless
* the path is already @scope/eslint or @scope/eslint-config-xxx * the path is already @scope/eslint or @scope/eslint-config-xxx
*/ */
name = name.replace(/^@([^\/]+)\/(.*)$/, "@$1/" + prefix + "-$2"); name = name.replace(/^@([^\/]+)\/(.*)$/, `@$1/${prefix}-$2`);
} }
} else if (name.indexOf(prefix + "-") !== 0) { } else if (name.indexOf(`${prefix}-`) !== 0) {
name = prefix + "-" + name; name = `${prefix}-${name}`;
} }
return name; return name;
@ -469,12 +469,12 @@ function resolve(filePath, relativeTo) {
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1); const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
normalizedPackageName = normalizePackageName(packagePath, "eslint-plugin"); normalizedPackageName = normalizePackageName(packagePath, "eslint-plugin");
debug("Attempting to resolve " + normalizedPackageName); debug(`Attempting to resolve ${normalizedPackageName}`);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath, configName }; return { filePath, configName };
} else { } else {
normalizedPackageName = normalizePackageName(filePath, "eslint-config"); normalizedPackageName = normalizePackageName(filePath, "eslint-config");
debug("Attempting to resolve " + normalizedPackageName); debug(`Attempting to resolve ${normalizedPackageName}`);
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
return { filePath }; return { filePath };
} }

18
tools/eslint/lib/config/config-initializer.js

@ -44,8 +44,8 @@ function writeFile(config, format) {
extname = ".json"; extname = ".json";
} }
ConfigFile.write(config, "./.eslintrc" + extname); ConfigFile.write(config, `./.eslintrc${extname}`);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd()); log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`);
if (config.installedESLint) { if (config.installedESLint) {
log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy."); log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy.");
@ -63,11 +63,11 @@ function installModules(config) {
// Create a list of modules which should be installed based on config // Create a list of modules which should be installed based on config
if (config.plugins) { if (config.plugins) {
modules = modules.concat(config.plugins.map(function(name) { modules = modules.concat(config.plugins.map(function(name) {
return "eslint-plugin-" + name; return `eslint-plugin-${name}`;
})); }));
} }
if (config.extends && config.extends.indexOf("eslint:") === -1) { if (config.extends && config.extends.indexOf("eslint:") === -1) {
modules.push("eslint-config-" + config.extends); modules.push(`eslint-config-${config.extends}`);
} }
// Determine which modules are already installed // Determine which modules are already installed
@ -93,7 +93,7 @@ function installModules(config) {
}); });
if (modulesToInstall.length > 0) { if (modulesToInstall.length > 0) {
log.info("Installing " + modulesToInstall.join(", ")); log.info(`Installing ${modulesToInstall.join(", ")}`);
npmUtil.installSyncSaveDev(modulesToInstall); npmUtil.installSyncSaveDev(modulesToInstall);
} }
} }
@ -150,7 +150,7 @@ function configureRules(answers, config) {
registry = registry.lintSourceCode(sourceCodes, newConfig, function(total) { registry = registry.lintSourceCode(sourceCodes, newConfig, function(total) {
bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning
}); });
debug("\nRegistry: " + util.inspect(registry.rules, {depth: null})); debug(`\nRegistry: ${util.inspect(registry.rules, {depth: null})}`);
// Create a list of recommended rules, because we don't want to disable them // Create a list of recommended rules, because we don't want to disable them
const recRules = Object.keys(recConfig.rules).filter(function(ruleId) { const recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
@ -198,9 +198,9 @@ function configureRules(answers, config) {
return (newConfig.rules[ruleId] !== 0); return (newConfig.rules[ruleId] !== 0);
}).length; }).length;
const resultMessage = [ const resultMessage = [
"\nEnabled " + enabledRules + " out of " + totalRules, `\nEnabled ${enabledRules} out of ${totalRules}`,
"rules based on " + fileQty, `rules based on ${fileQty}`,
"file" + ((fileQty === 1) ? "." : "s.") `file${(fileQty === 1) ? "." : "s."}`
].join(" "); ].join(" ");
log.info(resultMessage); log.info(resultMessage);

2
tools/eslint/lib/config/config-ops.js

@ -63,7 +63,7 @@ module.exports = {
const environment = Environments.get(name); const environment = Environments.get(name);
if (environment) { if (environment) {
debug("Creating config for environment " + name); debug(`Creating config for environment ${name}`);
if (environment.globals) { if (environment.globals) {
Object.assign(envConfig.globals, environment.globals); Object.assign(envConfig.globals, environment.globals);
} }

2
tools/eslint/lib/config/environments.js

@ -66,7 +66,7 @@ module.exports = {
importPlugin(plugin, pluginName) { importPlugin(plugin, pluginName) {
if (plugin.environments) { if (plugin.environments) {
Object.keys(plugin.environments).forEach(function(envName) { Object.keys(plugin.environments).forEach(function(envName) {
this.define(pluginName + "/" + envName, plugin.environments[envName]); this.define(`${pluginName}/${envName}`, plugin.environments[envName]);
}, this); }, this);
} }
}, },

6
tools/eslint/lib/config/plugins.js

@ -115,7 +115,7 @@ module.exports = {
let plugin = null; let plugin = null;
if (pluginName.match(/\s+/)) { if (pluginName.match(/\s+/)) {
const whitespaceError = new Error("Whitespace found in plugin name '" + pluginName + "'"); const whitespaceError = new Error(`Whitespace found in plugin name '${pluginName}'`);
whitespaceError.messageTemplate = "whitespace-found"; whitespaceError.messageTemplate = "whitespace-found";
whitespaceError.messageData = { whitespaceError.messageData = {
@ -128,8 +128,8 @@ module.exports = {
try { try {
plugin = require(longName); plugin = require(longName);
} catch (err) { } catch (err) {
debug("Failed to load plugin " + longName + "."); debug(`Failed to load plugin ${longName}.`);
err.message = "Failed to load plugin " + pluginName + ": " + err.message; err.message = `Failed to load plugin ${pluginName}: ${err.message}`;
err.messageTemplate = "plugin-missing"; err.messageTemplate = "plugin-missing";
err.messageData = { err.messageData = {
pluginName: longName pluginName: longName

38
tools/eslint/lib/eslint.js

@ -97,7 +97,7 @@ function parseJsonConfig(string, location, messages) {
items = {}; items = {};
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,"); string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
try { try {
items = JSON.parse("{" + string + "}"); items = JSON.parse(`{${string}}`);
} catch (ex) { } catch (ex) {
messages.push({ messages.push({
@ -105,7 +105,7 @@ function parseJsonConfig(string, location, messages) {
fatal: true, fatal: true,
severity: 2, severity: 2,
source: null, source: null,
message: "Failed to parse JSON from '" + string + "': " + ex.message, message: `Failed to parse JSON from '${string}': ${ex.message}`,
line: location.start.line, line: location.start.line,
column: location.start.column + 1 column: location.start.column + 1
}); });
@ -350,7 +350,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
Object.keys(items).forEach(function(name) { Object.keys(items).forEach(function(name) {
const ruleValue = items[name]; const ruleValue = items[name];
validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line); validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`);
commentRules[name] = ruleValue; commentRules[name] = ruleValue;
}); });
break; break;
@ -446,7 +446,7 @@ function prepareConfig(config) {
const rule = config.rules[k]; const rule = config.rules[k];
if (rule === null) { if (rule === null) {
throw new Error("Invalid config for rule '" + k + "'\."); throw new Error(`Invalid config for rule '${k}'\.`);
} }
if (Array.isArray(rule)) { if (Array.isArray(rule)) {
copiedRules[k] = rule.slice(); copiedRules[k] = rule.slice();
@ -527,7 +527,7 @@ function getRuleReplacementMessage(ruleId) {
if (ruleId in replacements.rules) { if (ruleId in replacements.rules) {
const newRules = replacements.rules[ruleId]; const newRules = replacements.rules[ruleId];
return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", "); return `Rule '${ruleId}' was removed and replaced by: ${newRules.join(", ")}`;
} }
return null; return null;
@ -585,7 +585,6 @@ module.exports = (function() {
let messages = [], let messages = [],
currentConfig = null, currentConfig = null,
currentScopes = null, currentScopes = null,
scopeMap = null,
scopeManager = null, scopeManager = null,
currentFilename = null, currentFilename = null,
traverser = null, traverser = null,
@ -655,7 +654,7 @@ module.exports = (function() {
fatal: true, fatal: true,
severity: 2, severity: 2,
source, source,
message: "Parsing error: " + message, message: `Parsing error: ${message}`,
line: ex.lineNumber, line: ex.lineNumber,
column: ex.column column: ex.column
@ -706,7 +705,6 @@ module.exports = (function() {
messages = []; messages = [];
currentConfig = null; currentConfig = null;
currentScopes = null; currentScopes = null;
scopeMap = null;
scopeManager = null; scopeManager = null;
traverser = null; traverser = null;
reportingConfig = []; reportingConfig = [];
@ -783,7 +781,7 @@ module.exports = (function() {
ast = parse( ast = parse(
stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, function(match, captured) { stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, function(match, captured) {
shebang = captured; shebang = captured;
return "//" + captured; return `//${captured}`;
}), }),
config, config,
currentFilename currentFilename
@ -823,7 +821,7 @@ module.exports = (function() {
if (replacementMsg) { if (replacementMsg) {
ruleCreator = createStubRule(replacementMsg); ruleCreator = createStubRule(replacementMsg);
} else { } else {
ruleCreator = createStubRule("Definition for rule '" + key + "' was not found"); ruleCreator = createStubRule(`Definition for rule '${key}' was not found`);
} }
rules.define(key, ruleCreator); rules.define(key, ruleCreator);
} }
@ -847,7 +845,7 @@ module.exports = (function() {
); );
}); });
} catch (ex) { } catch (ex) {
ex.message = "Error while loading rule '" + key + "': " + ex.message; ex.message = `Error while loading rule '${key}': ${ex.message}`;
throw ex; throw ex;
} }
}); });
@ -871,24 +869,6 @@ module.exports = (function() {
currentScopes = scopeManager.scopes; currentScopes = scopeManager.scopes;
/*
* Index the scopes by the start range of their block for efficient
* lookup in getScope.
*/
scopeMap = [];
currentScopes.forEach(function(scope, index) {
const range = scope.block.range[0];
/*
* Sometimes two scopes are returned for a given node. This is
* handled later in a known way, so just don't overwrite here.
*/
if (!scopeMap[range]) {
scopeMap[range] = index;
}
});
// augment global scope with declared global variables // augment global scope with declared global variables
addDeclaredGlobals(ast, currentScopes[0], currentConfig); addDeclaredGlobals(ast, currentScopes[0], currentConfig);

15
tools/eslint/lib/formatters/checkstyle.js

@ -38,15 +38,16 @@ module.exports = function(results) {
results.forEach(function(result) { results.forEach(function(result) {
const messages = result.messages; const messages = result.messages;
output += "<file name=\"" + xmlEscape(result.filePath) + "\">"; output += `<file name="${xmlEscape(result.filePath)}">`;
messages.forEach(function(message) { messages.forEach(function(message) {
output += "<error line=\"" + xmlEscape(message.line) + "\" " + output += [
"column=\"" + xmlEscape(message.column) + "\" " + `<error line="${xmlEscape(message.line)}"`,
"severity=\"" + xmlEscape(getMessageType(message)) + "\" " + `column="${xmlEscape(message.column)}"`,
"message=\"" + xmlEscape(message.message) + `severity="${xmlEscape(getMessageType(message))}"`,
(message.ruleId ? " (" + message.ruleId + ")" : "") + "\" " + `message="${xmlEscape(message.message)}${message.ruleId ? ` (${message.ruleId})` : ""}"`,
"source=\"" + (message.ruleId ? xmlEscape("eslint.rules." + message.ruleId) : "") + "\" />"; `source="${message.ruleId ? xmlEscape(`eslint.rules.${message.ruleId}`) : ""}" />`
].join(" ");
}); });
output += "</file>"; output += "</file>";

14
tools/eslint/lib/formatters/compact.js

@ -40,12 +40,12 @@ module.exports = function(results) {
messages.forEach(function(message) { messages.forEach(function(message) {
output += result.filePath + ": "; output += `${result.filePath}: `;
output += "line " + (message.line || 0); output += `line ${message.line || 0}`;
output += ", col " + (message.column || 0); output += `, col ${message.column || 0}`;
output += ", " + getMessageType(message); output += `, ${getMessageType(message)}`;
output += " - " + message.message; output += ` - ${message.message}`;
output += message.ruleId ? " (" + message.ruleId + ")" : ""; output += message.ruleId ? ` (${message.ruleId})` : "";
output += "\n"; output += "\n";
}); });
@ -53,7 +53,7 @@ module.exports = function(results) {
}); });
if (total > 0) { if (total > 0) {
output += "\n" + total + " problem" + (total !== 1 ? "s" : ""); output += `\n${total} problem${total !== 1 ? "s" : ""}`;
} }
return output; return output;

6
tools/eslint/lib/formatters/html.js

@ -23,7 +23,7 @@ const resultTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "htm
* @returns {string} The original word with an s on the end if count is not one. * @returns {string} The original word with an s on the end if count is not one.
*/ */
function pluralize(word, count) { function pluralize(word, count) {
return (count === 1 ? word : word + "s"); return (count === 1 ? word : `${word}s`);
} }
/** /**
@ -34,10 +34,10 @@ function pluralize(word, count) {
*/ */
function renderSummary(totalErrors, totalWarnings) { function renderSummary(totalErrors, totalWarnings) {
const totalProblems = totalErrors + totalWarnings; const totalProblems = totalErrors + totalWarnings;
let renderedText = totalProblems + " " + pluralize("problem", totalProblems); let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`;
if (totalProblems !== 0) { if (totalProblems !== 0) {
renderedText += " (" + totalErrors + " " + pluralize("error", totalErrors) + ", " + totalWarnings + " " + pluralize("warning", totalWarnings) + ")"; renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`;
} }
return renderedText; return renderedText;
} }

13
tools/eslint/lib/formatters/jslint-xml.js

@ -20,14 +20,15 @@ module.exports = function(results) {
results.forEach(function(result) { results.forEach(function(result) {
const messages = result.messages; const messages = result.messages;
output += "<file name=\"" + result.filePath + "\">"; output += `<file name="${result.filePath}">`;
messages.forEach(function(message) { messages.forEach(function(message) {
output += "<issue line=\"" + message.line + "\" " + output += [
"char=\"" + message.column + "\" " + `<issue line="${message.line}"`,
"evidence=\"" + xmlEscape(message.source || "") + "\" " + `char="${message.column}"`,
"reason=\"" + xmlEscape(message.message || "") + `evidence="${xmlEscape(message.source || "")}"`,
(message.ruleId ? " (" + message.ruleId + ")" : "") + "\" />"; `reason="${xmlEscape(message.message || "")}${message.ruleId ? ` (${message.ruleId})` : ""}" />`
].join(" ");
}); });
output += "</file>"; output += "</file>";

16
tools/eslint/lib/formatters/junit.js

@ -40,21 +40,21 @@ module.exports = function(results) {
const messages = result.messages; const messages = result.messages;
if (messages.length) { if (messages.length) {
output += "<testsuite package=\"org.eslint\" time=\"0\" tests=\"" + messages.length + "\" errors=\"" + messages.length + "\" name=\"" + result.filePath + "\">\n"; output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
} }
messages.forEach(function(message) { messages.forEach(function(message) {
const type = message.fatal ? "error" : "failure"; const type = message.fatal ? "error" : "failure";
output += "<testcase time=\"0\" name=\"org.eslint." + (message.ruleId || "unknown") + "\">"; output += `<testcase time="0" name="org.eslint.${message.ruleId || "unknown"}">`;
output += "<" + type + " message=\"" + xmlEscape(message.message || "") + "\">"; output += `<${type} message="${xmlEscape(message.message || "")}">`;
output += "<![CDATA["; output += "<![CDATA[";
output += "line " + (message.line || 0) + ", col "; output += `line ${message.line || 0}, col `;
output += (message.column || 0) + ", " + getMessageType(message); output += `${message.column || 0}, ${getMessageType(message)}`;
output += " - " + xmlEscape(message.message || ""); output += ` - ${xmlEscape(message.message || "")}`;
output += (message.ruleId ? " (" + message.ruleId + ")" : ""); output += (message.ruleId ? ` (${message.ruleId})` : "");
output += "]]>"; output += "]]>";
output += "</" + type + ">"; output += `</${type}>`;
output += "</testcase>\n"; output += "</testcase>\n";
}); });

10
tools/eslint/lib/formatters/stylish.js

@ -18,7 +18,7 @@ const chalk = require("chalk"),
* @returns {string} The original word with an s on the end if count is not one. * @returns {string} The original word with an s on the end if count is not one.
*/ */
function pluralize(word, count) { function pluralize(word, count) {
return (count === 1 ? word : word + "s"); return (count === 1 ? word : `${word}s`);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -41,9 +41,9 @@ module.exports = function(results) {
} }
total += messages.length; total += messages.length;
output += chalk.underline(result.filePath) + "\n"; output += `${chalk.underline(result.filePath)}\n`;
output += table( output += `${table(
messages.map(function(message) { messages.map(function(message) {
let messageType; let messageType;
@ -73,9 +73,9 @@ module.exports = function(results) {
} }
).split("\n").map(function(el) { ).split("\n").map(function(el) {
return el.replace(/(\d+)\s+(\d+)/, function(m, p1, p2) { return el.replace(/(\d+)\s+(\d+)/, function(m, p1, p2) {
return chalk.dim(p1 + ":" + p2); return chalk.dim(`${p1}:${p2}`);
}); });
}).join("\n") + "\n\n"; }).join("\n")}\n\n`;
}); });
if (total > 0) { if (total > 0) {

6
tools/eslint/lib/formatters/table.js

@ -97,7 +97,7 @@ function drawReport(results) {
return ""; return "";
} }
return "\n" + result.filePath + "\n\n" + drawTable(result.messages); return `\n${result.filePath}\n\n${drawTable(result.messages)}`;
}); });
files = files.filter(function(content) { files = files.filter(function(content) {
@ -129,7 +129,7 @@ module.exports = function(report) {
result = drawReport(report); result = drawReport(report);
} }
result += "\n" + table([ result += `\n${table([
[ [
chalk.red(pluralize("Error", errorCount, true)) chalk.red(pluralize("Error", errorCount, true))
], ],
@ -146,7 +146,7 @@ module.exports = function(report) {
drawHorizontalLine() { drawHorizontalLine() {
return true; return true;
} }
}); })}`;
return result; return result;
}; };

8
tools/eslint/lib/formatters/tap.js

@ -30,9 +30,9 @@ function getMessageType(message) {
*/ */
function outputDiagnostics(diagnostic) { function outputDiagnostics(diagnostic) {
const prefix = " "; const prefix = " ";
let output = prefix + "---\n"; let output = `${prefix}---\n`;
output += prefix + yaml.safeDump(diagnostic).split("\n").join("\n" + prefix); output += prefix + yaml.safeDump(diagnostic).split("\n").join(`\n${prefix}`);
output += "...\n"; output += "...\n";
return output; return output;
} }
@ -42,7 +42,7 @@ function outputDiagnostics(diagnostic) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
module.exports = function(results) { module.exports = function(results) {
let output = "TAP version 13\n1.." + results.length + "\n"; let output = `TAP version 13\n1..${results.length}\n`;
results.forEach(function(result, id) { results.forEach(function(result, id) {
const messages = result.messages; const messages = result.messages;
@ -77,7 +77,7 @@ module.exports = function(results) {
}); });
} }
output += testResult + " " + (id + 1) + " - " + result.filePath + "\n"; output += `${testResult} ${id + 1} - ${result.filePath}\n`;
// If we have an error include diagnostics // If we have an error include diagnostics
if (messages.length > 0) { if (messages.length > 0) {

13
tools/eslint/lib/formatters/unix.js

@ -39,12 +39,11 @@ module.exports = function(results) {
messages.forEach(function(message) { messages.forEach(function(message) {
output += result.filePath + ":"; output += `${result.filePath}:`;
output += (message.line || 0) + ":"; output += `${message.line || 0}:`;
output += (message.column || 0) + ":"; output += `${message.column || 0}:`;
output += " " + message.message + " "; output += ` ${message.message} `;
output += "[" + getMessageType(message) + output += `[${getMessageType(message)}${message.ruleId ? `/${message.ruleId}` : ""}]`;
(message.ruleId ? "/" + message.ruleId : "") + "]";
output += "\n"; output += "\n";
}); });
@ -52,7 +51,7 @@ module.exports = function(results) {
}); });
if (total > 0) { if (total > 0) {
output += "\n" + total + " problem" + (total !== 1 ? "s" : ""); output += `\n${total} problem${total !== 1 ? "s" : ""}`;
} }
return output; return output;

12
tools/eslint/lib/formatters/visualstudio.js

@ -42,11 +42,11 @@ module.exports = function(results) {
messages.forEach(function(message) { messages.forEach(function(message) {
output += result.filePath; output += result.filePath;
output += "(" + (message.line || 0); output += `(${message.line || 0}`;
output += message.column ? "," + message.column : ""; output += message.column ? `,${message.column}` : "";
output += "): " + getMessageType(message); output += `): ${getMessageType(message)}`;
output += message.ruleId ? " " + message.ruleId : ""; output += message.ruleId ? ` ${message.ruleId}` : "";
output += " : " + message.message; output += ` : ${message.message}`;
output += "\n"; output += "\n";
}); });
@ -56,7 +56,7 @@ module.exports = function(results) {
if (total === 0) { if (total === 0) {
output += "no problems"; output += "no problems";
} else { } else {
output += "\n" + total + " problem" + (total !== 1 ? "s" : ""); output += `\n${total} problem${total !== 1 ? "s" : ""}`;
} }
return output; return output;

8
tools/eslint/lib/ignored-paths.js

@ -136,16 +136,16 @@ function IgnoredPaths(options) {
fs.statSync(options.ignorePath); fs.statSync(options.ignorePath);
ignorePath = options.ignorePath; ignorePath = options.ignorePath;
} catch (e) { } catch (e) {
e.message = "Cannot read ignore file: " + options.ignorePath + "\nError: " + e.message; e.message = `Cannot read ignore file: ${options.ignorePath}\nError: ${e.message}`;
throw e; throw e;
} }
} else { } else {
debug("Looking for ignore file in " + options.cwd); debug(`Looking for ignore file in ${options.cwd}`);
ignorePath = findIgnoreFile(options.cwd); ignorePath = findIgnoreFile(options.cwd);
try { try {
fs.statSync(ignorePath); fs.statSync(ignorePath);
debug("Loaded ignore file " + ignorePath); debug(`Loaded ignore file ${ignorePath}`);
} catch (e) { } catch (e) {
debug("Could not find ignore file in cwd"); debug("Could not find ignore file in cwd");
this.options = options; this.options = options;
@ -153,7 +153,7 @@ function IgnoredPaths(options) {
} }
if (ignorePath) { if (ignorePath) {
debug("Adding " + ignorePath); debug(`Adding ${ignorePath}`);
this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath)); this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
addIgnoreFile(this.ig.custom, ignorePath); addIgnoreFile(this.ig.custom, ignorePath);
addIgnoreFile(this.ig.default, ignorePath); addIgnoreFile(this.ig.default, ignorePath);

3
tools/eslint/lib/internal-rules/.eslintrc.yml

@ -0,0 +1,3 @@
rules:
internal-no-invalid-meta: "error"
internal-consistent-docs-description: "error"

131
tools/eslint/lib/internal-rules/internal-consistent-docs-description.js

@ -0,0 +1,131 @@
/**
* @fileoverview Internal rule to enforce meta.docs.description conventions.
* @author Vitor Balocco
*/
"use strict";
const ALLOWED_FIRST_WORDS = [
"enforce",
"require",
"disallow"
];
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Gets the property of the Object node passed in that has the name specified.
*
* @param {string} property Name of the property to return.
* @param {ASTNode} node The ObjectExpression node.
* @returns {ASTNode} The Property node or null if not found.
*/
function getPropertyFromObject(property, node) {
const properties = node.properties;
for (let i = 0; i < properties.length; i++) {
if (properties[i].key.name === property) {
return properties[i];
}
}
return null;
}
/**
* Verifies that the meta.docs.description property follows our internal conventions.
*
* @param {RuleContext} context The ESLint rule context.
* @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
* @returns {void}
*/
function checkMetaDocsDescription(context, exportsNode) {
if (exportsNode.type !== "ObjectExpression") {
// if the exported node is not the correct format, "internal-no-invalid-meta" will already report this.
return;
}
const metaProperty = getPropertyFromObject("meta", exportsNode);
const metaDocs = metaProperty && getPropertyFromObject("docs", metaProperty.value);
const metaDocsDescription = metaDocs && getPropertyFromObject("description", metaDocs.value);
if (!metaDocsDescription) {
// if there is no `meta.docs.description` property, "internal-no-invalid-meta" will already report this.
return;
}
const description = metaDocsDescription.value.value;
if (typeof description !== "string") {
context.report({
node: metaDocsDescription.value,
message: "`meta.docs.description` should be a string."
});
return;
}
if (description === "") {
context.report({
node: metaDocsDescription.value,
message: "`meta.docs.description` should not be empty.",
});
return;
}
if (description.indexOf(" ") === 0) {
context.report({
node: metaDocsDescription.value,
message: "`meta.docs.description` should not start with whitespace."
});
return;
}
const firstWord = description.split(" ")[0];
if (ALLOWED_FIRST_WORDS.indexOf(firstWord) === -1) {
context.report({
node: metaDocsDescription.value,
message: "`meta.docs.description` should start with one of the following words: {{ allowedWords }}. Started with \"{{ firstWord }}\" instead.",
data: {
allowedWords: ALLOWED_FIRST_WORDS.join(", "),
firstWord
}
});
return;
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "enforce correct conventions of `meta.docs.description` property in core rules",
category: "Internal",
recommended: false
},
schema: []
},
create(context) {
return {
AssignmentExpression(node) {
if (node.left &&
node.right &&
node.left.type === "MemberExpression" &&
node.left.object.name === "module" &&
node.left.property.name === "exports") {
checkMetaDocsDescription(context, node.right);
}
}
};
}
};

4
tools/eslint/lib/options.js

@ -216,8 +216,8 @@ module.exports = optionator({
}, },
{ {
option: "print-config", option: "print-config",
type: "Boolean", type: "path::String",
description: "Print the configuration to be used" description: "Print the configuration for the given file"
} }
] ]
}); });

2
tools/eslint/lib/rules.js

@ -54,7 +54,7 @@ function load(rulesDir, cwd) {
function importPlugin(plugin, pluginName) { function importPlugin(plugin, pluginName) {
if (plugin.rules) { if (plugin.rules) {
Object.keys(plugin.rules).forEach(function(ruleId) { Object.keys(plugin.rules).forEach(function(ruleId) {
const qualifiedRuleId = pluginName + "/" + ruleId, const qualifiedRuleId = `${pluginName}/${ruleId}`,
rule = plugin.rules[ruleId]; rule = plugin.rules[ruleId];
define(qualifiedRuleId, rule); define(qualifiedRuleId, rule);

1
tools/eslint/lib/rules/.eslintrc.yml

@ -1,2 +1,3 @@
rules: rules:
internal-no-invalid-meta: "error" internal-no-invalid-meta: "error"
internal-consistent-docs-description: "error"

4
tools/eslint/lib/rules/array-callback-return.js

@ -185,7 +185,9 @@ module.exports = {
shouldCheck: shouldCheck:
TARGET_NODE_TYPE.test(node.type) && TARGET_NODE_TYPE.test(node.type) &&
node.body.type === "BlockStatement" && node.body.type === "BlockStatement" &&
isCallbackOfArrayMethod(node) isCallbackOfArrayMethod(node) &&
!node.async &&
!node.generator
}; };
}, },

2
tools/eslint/lib/rules/arrow-parens.js

@ -51,7 +51,7 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function parens(node) { function parens(node) {
const token = sourceCode.getFirstToken(node); const token = sourceCode.getFirstToken(node, node.async ? 1 : 0);
// "as-needed", { "requireForBlockBody": true }: x => x // "as-needed", { "requireForBlockBody": true }: x => x
if ( if (

28
tools/eslint/lib/rules/class-methods-use-this.js

@ -16,9 +16,23 @@ module.exports = {
category: "Best Practices", category: "Best Practices",
recommended: false recommended: false
}, },
schema: [] schema: [{
type: "object",
properties: {
exceptMethods: {
type: "array",
items: {
type: "string"
}
}
},
additionalProperties: false
}]
}, },
create(context) { create(context) {
const config = context.options[0] ? Object.assign({}, context.options[0]) : {};
const exceptMethods = new Set(config.exceptMethods || []);
const stack = []; const stack = [];
/** /**
@ -41,6 +55,16 @@ module.exports = {
return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition"; return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
} }
/**
* Check if the node is an instance method not excluded by config
* @param {ASTNode} node - node to check
* @returns {boolean} True if it is an instance method, and not excluded by config
* @private
*/
function isIncludedInstanceMethod(node) {
return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
}
/** /**
* Checks if we are leaving a function that is a method, and reports if 'this' has not been used. * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
* Static methods and the constructor are exempt. * Static methods and the constructor are exempt.
@ -52,7 +76,7 @@ module.exports = {
function exitFunction(node) { function exitFunction(node) {
const methodUsesThis = stack.pop(); const methodUsesThis = stack.pop();
if (isInstanceMethod(node.parent) && !methodUsesThis) { if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
context.report({ context.report({
node, node,
message: "Expected 'this' to be used by class method '{{classMethod}}'.", message: "Expected 'this' to be used by class method '{{classMethod}}'.",

244
tools/eslint/lib/rules/comma-dangle.js

@ -11,16 +11,62 @@
const lodash = require("lodash"); const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const DEFAULT_OPTIONS = Object.freeze({
arrays: "never",
objects: "never",
imports: "never",
exports: "never",
functions: "ignore",
});
/** /**
* Checks whether or not a trailing comma is allowed in a given node. * Checks whether or not a trailing comma is allowed in a given node.
* `ArrayPattern` which has `RestElement` disallows it. * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
* *
* @param {ASTNode} node - A node to check.
* @param {ASTNode} lastItem - The node of the last element in the given node. * @param {ASTNode} lastItem - The node of the last element in the given node.
* @returns {boolean} `true` if a trailing comma is allowed. * @returns {boolean} `true` if a trailing comma is allowed.
*/ */
function isTrailingCommaAllowed(node, lastItem) { function isTrailingCommaAllowed(lastItem) {
return node.type !== "ArrayPattern" || lastItem.type !== "RestElement"; return !(
lastItem.type === "RestElement" ||
lastItem.type === "RestProperty" ||
lastItem.type === "ExperimentalRestProperty"
);
}
/**
* Normalize option value.
*
* @param {string|Object|undefined} optionValue - The 1st option value to normalize.
* @returns {Object} The normalized option value.
*/
function normalizeOptions(optionValue) {
if (typeof optionValue === "string") {
return {
arrays: optionValue,
objects: optionValue,
imports: optionValue,
exports: optionValue,
// For backward compatibility, always ignore functions.
functions: "ignore",
};
}
if (typeof optionValue === "object" && optionValue !== null) {
return {
arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays,
objects: optionValue.objects || DEFAULT_OPTIONS.objects,
imports: optionValue.imports || DEFAULT_OPTIONS.imports,
exports: optionValue.exports || DEFAULT_OPTIONS.exports,
functions: optionValue.functions || DEFAULT_OPTIONS.functions,
};
}
return DEFAULT_OPTIONS;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -39,16 +85,112 @@ module.exports = {
schema: [ schema: [
{ {
enum: ["always", "always-multiline", "only-multiline", "never"] defs: {
} value: {
enum: [
"always",
"always-multiline",
"only-multiline",
"never"
]
},
valueWithIgnore: {
anyOf: [
{
$ref: "#/defs/value"
},
{
enum: ["ignore"]
}
]
}
},
anyOf: [
{
$ref: "#/defs/value"
},
{
type: "object",
properties: {
arrays: {$refs: "#/defs/valueWithIgnore"},
objects: {$refs: "#/defs/valueWithIgnore"},
imports: {$refs: "#/defs/valueWithIgnore"},
exports: {$refs: "#/defs/valueWithIgnore"},
functions: {$refs: "#/defs/valueWithIgnore"}
},
additionalProperties: false
}
]
},
] ]
}, },
create(context) { create(context) {
const mode = context.options[0]; const options = normalizeOptions(context.options[0]);
const sourceCode = context.getSourceCode();
const UNEXPECTED_MESSAGE = "Unexpected trailing comma."; const UNEXPECTED_MESSAGE = "Unexpected trailing comma.";
const MISSING_MESSAGE = "Missing trailing comma."; const MISSING_MESSAGE = "Missing trailing comma.";
/**
* Gets the last item of the given node.
* @param {ASTNode} node - The node to get.
* @returns {ASTNode|null} The last node or null.
*/
function getLastItem(node) {
switch (node.type) {
case "ObjectExpression":
case "ObjectPattern":
return lodash.last(node.properties);
case "ArrayExpression":
case "ArrayPattern":
return lodash.last(node.elements);
case "ImportDeclaration":
case "ExportNamedDeclaration":
return lodash.last(node.specifiers);
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
return lodash.last(node.params);
case "CallExpression":
case "NewExpression":
return lodash.last(node.arguments);
default:
return null;
}
}
/**
* Gets the trailing comma token of the given node.
* If the trailing comma does not exist, this returns the token which is
* the insertion point of the trailing comma token.
*
* @param {ASTNode} node - The node to get.
* @param {ASTNode} lastItem - The last item of the node.
* @returns {Token} The trailing comma token or the insertion point.
*/
function getTrailingToken(node, lastItem) {
switch (node.type) {
case "ObjectExpression":
case "ObjectPattern":
case "ArrayExpression":
case "ArrayPattern":
case "CallExpression":
case "NewExpression":
return sourceCode.getLastToken(node, 1);
case "FunctionDeclaration":
case "FunctionExpression":
return sourceCode.getTokenBefore(node.body, 1);
default: {
const nextToken = sourceCode.getTokenAfter(lastItem);
if (nextToken.value === ",") {
return nextToken;
}
return sourceCode.getLastToken(lastItem);
}
}
}
/** /**
* Checks whether or not a given node is multiline. * Checks whether or not a given node is multiline.
* This rule handles a given node as multiline when the closing parenthesis * This rule handles a given node as multiline when the closing parenthesis
@ -58,26 +200,14 @@ module.exports = {
* @returns {boolean} `true` if the node is multiline. * @returns {boolean} `true` if the node is multiline.
*/ */
function isMultiline(node) { function isMultiline(node) {
const lastItem = lodash.last(node.properties || node.elements || node.specifiers); const lastItem = getLastItem(node);
if (!lastItem) { if (!lastItem) {
return false; return false;
} }
const sourceCode = context.getSourceCode(); const penultimateToken = getTrailingToken(node, lastItem);
let penultimateToken = sourceCode.getLastToken(lastItem), const lastToken = sourceCode.getTokenAfter(penultimateToken);
lastToken = sourceCode.getTokenAfter(penultimateToken);
// parentheses are a pain
while (lastToken.value === ")") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
}
if (lastToken.value === ",") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
}
return lastToken.loc.end.line !== penultimateToken.loc.end.line; return lastToken.loc.end.line !== penultimateToken.loc.end.line;
} }
@ -91,21 +221,13 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function forbidTrailingComma(node) { function forbidTrailingComma(node) {
const lastItem = lodash.last(node.properties || node.elements || node.specifiers); const lastItem = getLastItem(node);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return; return;
} }
const sourceCode = context.getSourceCode(); const trailingToken = getTrailingToken(node, lastItem);
let 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 {
trailingToken = sourceCode.getTokenAfter(lastItem);
}
if (trailingToken.value === ",") { if (trailingToken.value === ",") {
context.report({ context.report({
@ -132,33 +254,25 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function forceTrailingComma(node) { function forceTrailingComma(node) {
const lastItem = lodash.last(node.properties || node.elements || node.specifiers); const lastItem = getLastItem(node);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return; return;
} }
if (!isTrailingCommaAllowed(node, lastItem)) { if (!isTrailingCommaAllowed(lastItem)) {
forbidTrailingComma(node); forbidTrailingComma(node);
return; return;
} }
const sourceCode = context.getSourceCode(); const trailingToken = getTrailingToken(node, lastItem);
let penultimateToken = lastItem,
trailingToken = sourceCode.getTokenAfter(lastItem);
// Skip close parentheses.
while (trailingToken.value === ")") {
penultimateToken = trailingToken;
trailingToken = sourceCode.getTokenAfter(trailingToken);
}
if (trailingToken.value !== ",") { if (trailingToken.value !== ",") {
context.report({ context.report({
node: lastItem, node: lastItem,
loc: lastItem.loc.end, loc: trailingToken.loc.end,
message: MISSING_MESSAGE, message: MISSING_MESSAGE,
fix(fixer) { fix(fixer) {
return fixer.insertTextAfter(penultimateToken, ","); return fixer.insertTextAfter(trailingToken, ",");
} }
}); });
} }
@ -198,26 +312,30 @@ module.exports = {
} }
} }
// Chooses a checking function. const predicate = {
let checkForTrailingComma; always: forceTrailingComma,
"always-multiline": forceTrailingCommaIfMultiline,
if (mode === "always") { "only-multiline": allowTrailingCommaIfMultiline,
checkForTrailingComma = forceTrailingComma; never: forbidTrailingComma,
} else if (mode === "always-multiline") { ignore: lodash.noop,
checkForTrailingComma = forceTrailingCommaIfMultiline; };
} else if (mode === "only-multiline") {
checkForTrailingComma = allowTrailingCommaIfMultiline;
} else {
checkForTrailingComma = forbidTrailingComma;
}
return { return {
ObjectExpression: checkForTrailingComma, ObjectExpression: predicate[options.objects],
ObjectPattern: checkForTrailingComma, ObjectPattern: predicate[options.objects],
ArrayExpression: checkForTrailingComma,
ArrayPattern: checkForTrailingComma, ArrayExpression: predicate[options.arrays],
ImportDeclaration: checkForTrailingComma, ArrayPattern: predicate[options.arrays],
ExportNamedDeclaration: checkForTrailingComma
ImportDeclaration: predicate[options.imports],
ExportNamedDeclaration: predicate[options.exports],
FunctionDeclaration: predicate[options.functions],
FunctionExpression: predicate[options.functions],
ArrowFunctionExpression: predicate[options.functions],
CallExpression: predicate[options.functions],
NewExpression: predicate[options.functions],
}; };
} }
}; };

97
tools/eslint/lib/rules/curly.js

@ -9,6 +9,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const astUtils = require("../ast-utils"); const astUtils = require("../ast-utils");
const esUtils = require("esutils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
@ -48,7 +49,9 @@ module.exports = {
maxItems: 2 maxItems: 2
} }
] ]
} },
fixable: "code"
}, },
create(context) { create(context) {
@ -137,12 +140,13 @@ module.exports = {
/** /**
* Reports "Expected { after ..." error * Reports "Expected { after ..." error
* @param {ASTNode} node The node to report. * @param {ASTNode} node The node to report.
* @param {ASTNode} bodyNode The body node that is incorrectly missing curly brackets
* @param {string} name The name to report. * @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report. * @param {string} suffix Additional string to add to the end of a report.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function reportExpectedBraceError(node, name, suffix) { function reportExpectedBraceError(node, bodyNode, name, suffix) {
context.report({ context.report({
node, node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
@ -150,19 +154,73 @@ module.exports = {
data: { data: {
name, name,
suffix: (suffix ? ` ${suffix}` : "") suffix: (suffix ? ` ${suffix}` : "")
} },
fix: fixer => fixer.replaceText(bodyNode, `{${sourceCode.getText(bodyNode)}}`)
}); });
} }
/**
* Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError.
* @param {Token} closingBracket The } token
* @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block.
*/
function needsSemicolon(closingBracket) {
const tokenBefore = sourceCode.getTokenBefore(closingBracket);
const tokenAfter = sourceCode.getTokenAfter(closingBracket);
const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
if (tokenBefore.value === ";") {
// If the last statement already has a semicolon, don't add another one.
return false;
}
if (!tokenAfter) {
// If there are no statements after this block, there is no need to add a semicolon.
return false;
}
if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") {
// If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression),
// don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause
// a SyntaxError if it was followed by `else`.
return false;
}
if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) {
// If the next token is on the same line, insert a semicolon.
return true;
}
if (/^[(\[\/`+-]/.test(tokenAfter.value)) {
// If the next token starts with a character that would disrupt ASI, insert a semicolon.
return true;
}
if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) {
// If the last token is ++ or --, insert a semicolon to avoid disrupting ASI.
return true;
}
// Otherwise, do not insert a semicolon.
return false;
}
/** /**
* Reports "Unnecessary { after ..." error * Reports "Unnecessary { after ..." error
* @param {ASTNode} node The node to report. * @param {ASTNode} node The node to report.
* @param {ASTNode} bodyNode The block statement that is incorrectly surrounded by parens
* @param {string} name The name to report. * @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report. * @param {string} suffix Additional string to add to the end of a report.
* @returns {void} * @returns {void}
* @private * @private
*/ */
function reportUnnecessaryBraceError(node, name, suffix) { function reportUnnecessaryBraceError(node, bodyNode, name, suffix) {
context.report({ context.report({
node, node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
@ -170,6 +228,33 @@ module.exports = {
data: { data: {
name, name,
suffix: (suffix ? ` ${suffix}` : "") suffix: (suffix ? ` ${suffix}` : "")
},
fix(fixer) {
// `do while` expressions sometimes need a space to be inserted after `do`.
// e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
const needsPrecedingSpace = node.type === "DoWhileStatement" &&
sourceCode.getTokenBefore(bodyNode).end === bodyNode.start &&
esUtils.code.isIdentifierPartES6(sourceCode.getText(bodyNode).charCodeAt(1));
const openingBracket = sourceCode.getFirstToken(bodyNode);
const closingBracket = sourceCode.getLastToken(bodyNode);
const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket);
if (needsSemicolon(closingBracket)) {
/*
* If removing braces would cause a SyntaxError due to multiple statements on the same line (or
* change the semantics of the code due to ASI), don't perform a fix.
*/
return null;
}
const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) +
sourceCode.getText(lastTokenInBlock) +
sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]);
return fixer.replaceText(bodyNode, (needsPrecedingSpace ? " " : "") + resultingBodyText);
} }
}); });
} }
@ -218,9 +303,9 @@ module.exports = {
check() { check() {
if (this.expected !== null && this.expected !== this.actual) { if (this.expected !== null && this.expected !== this.actual) {
if (this.expected) { if (this.expected) {
reportExpectedBraceError(node, name, suffix); reportExpectedBraceError(node, body, name, suffix);
} else { } else {
reportUnnecessaryBraceError(node, name, suffix); reportUnnecessaryBraceError(node, body, name, suffix);
} }
} }
} }

22
tools/eslint/lib/rules/dot-location.js

@ -23,7 +23,9 @@ module.exports = {
{ {
enum: ["object", "property"] enum: ["object", "property"]
} }
] ],
fixable: "code"
}, },
create(context) { create(context) {
@ -44,14 +46,28 @@ module.exports = {
*/ */
function checkDotLocation(obj, prop, node) { function checkDotLocation(obj, prop, node) {
const dot = sourceCode.getTokenBefore(prop); const dot = sourceCode.getTokenBefore(prop);
const textBeforeDot = sourceCode.getText().slice(obj.range[1], dot.range[0]);
const textAfterDot = sourceCode.getText().slice(dot.range[1], prop.range[0]);
if (dot.type === "Punctuator" && dot.value === ".") { if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) { if (onObject) {
if (!astUtils.isTokenOnSameLine(obj, dot)) { if (!astUtils.isTokenOnSameLine(obj, dot)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as object."); const neededTextAfterObj = astUtils.isDecimalInteger(obj) ? " " : "";
context.report({
node,
loc: dot.loc.start,
message: "Expected dot to be on same line as object.",
fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${neededTextAfterObj}.${textBeforeDot}${textAfterDot}`)
});
} }
} else if (!astUtils.isTokenOnSameLine(dot, prop)) { } else if (!astUtils.isTokenOnSameLine(dot, prop)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as property."); context.report({
node,
loc: dot.loc.start,
message: "Expected dot to be on same line as property.",
fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${textBeforeDot}${textAfterDot}.`)
});
} }
} }
} }

66
tools/eslint/lib/rules/eol-last.js

@ -1,9 +1,15 @@
/** /**
* @fileoverview Require file to end with single newline. * @fileoverview Require or disallow newline at the end of files
* @author Nodeca Team <https://github.com/nodeca> * @author Nodeca Team <https://github.com/nodeca>
*/ */
"use strict"; "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -11,20 +17,17 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "enforce at least one newline at the end of files", description: "require or disallow newline at the end of files",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },
fixable: "whitespace", fixable: "whitespace",
schema: [ schema: [
{ {
enum: ["unix", "windows"] enum: ["always", "never", "unix", "windows"]
} }
] ]
}, },
create(context) { create(context) {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -32,31 +35,60 @@ module.exports = {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
Program: function checkBadEOF(node) { Program: function checkBadEOF(node) {
const sourceCode = context.getSourceCode(), const sourceCode = context.getSourceCode(),
src = sourceCode.getText(), src = sourceCode.getText(),
location = {column: 1}, location = {
linebreakStyle = context.options[0] || "unix", column: lodash.last(sourceCode.lines).length,
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n"; line: sourceCode.lines.length
},
LF = "\n",
CRLF = `\r${LF}`,
endsWithNewline = lodash.endsWith(src, LF);
let mode = context.options[0] || "always",
appendCRLF = false;
if (mode === "unix") {
// `"unix"` should behave exactly as `"always"`
mode = "always";
}
if (mode === "windows") {
if (src[src.length - 1] !== "\n") { // `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility
mode = "always";
appendCRLF = true;
}
if (mode === "always" && !endsWithNewline) {
// file is not newline-terminated // File is not newline-terminated, but should be
location.line = src.split(/\n/g).length;
context.report({ context.report({
node, node,
loc: location, loc: location,
message: "Newline required at end of file but not found.", message: "Newline required at end of file but not found.",
fix(fixer) { fix(fixer) {
return fixer.insertTextAfterRange([0, src.length], linebreak); return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF);
}
});
} else if (mode === "never" && endsWithNewline) {
// File is newline-terminated, but shouldn't be
context.report({
node,
loc: location,
message: "Newline not allowed at end of file.",
fix(fixer) {
const finalEOLs = /(?:\r?\n)+$/,
match = finalEOLs.exec(sourceCode.text),
start = match.index,
end = sourceCode.text.length;
return fixer.replaceTextRange([start, end], "");
} }
}); });
} }
} }
}; };
} }
}; };

2
tools/eslint/lib/rules/func-call-spacing.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require or disallow spacing between `function` identifiers and their invocations", description: "require or disallow spacing between function identifiers and their invocations",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

149
tools/eslint/lib/rules/func-name-matching.js

@ -0,0 +1,149 @@
/**
* @fileoverview Rule to require function names to match the name of the variable or property to which they are assigned.
* @author Annie Zhang, Pavel Strashkin
*/
"use strict";
//--------------------------------------------------------------------------
// Requirements
//--------------------------------------------------------------------------
const astUtils = require("../ast-utils");
const esutils = require("esutils");
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a pattern is `module.exports` or `module["exports"]`
* @param {ASTNode} pattern The left side of the AssignmentExpression
* @returns {boolean} True if the pattern is `module.exports` or `module["exports"]`
*/
function isModuleExports(pattern) {
if (pattern.type === "MemberExpression" && pattern.object.type === "Identifier" && pattern.object.name === "module") {
// module.exports
if (pattern.property.type === "Identifier" && pattern.property.name === "exports") {
return true;
}
// module["exports"]
if (pattern.property.type === "Literal" && pattern.property.value === "exports") {
return true;
}
}
return false;
}
/**
* Determines if a string name is a valid identifier
* @param {string} name The string to be checked
* @param {int} ecmaVersion The ECMAScript version if specified in the parserOptions config
* @returns {boolean} True if the string is a valid identifier
*/
function isIdentifier(name, ecmaVersion) {
if (ecmaVersion >= 6) {
return esutils.keyword.isIdentifierES6(name);
}
return esutils.keyword.isIdentifierES5(name);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "require function names to match the name of the variable or property to which they are assigned",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
type: "object",
properties: {
includeCommonJSModuleExports: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
create(context) {
const includeModuleExports = context.options[0] && context.options[0].includeCommonJSModuleExports;
const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
/**
* Reports
* @param {ASTNode} node The node to report
* @param {string} name The variable or property name
* @param {string} funcName The function name
* @param {boolean} isProp True if the reported node is a property assignment
* @returns {void}
*/
function report(node, name, funcName, isProp) {
context.report({
node,
message: isProp ? "Function name `{{funcName}}` should match property name `{{name}}`"
: "Function name `{{funcName}}` should match variable name `{{name}}`",
data: {
name,
funcName
}
});
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
VariableDeclarator(node) {
if (!node.init || node.init.type !== "FunctionExpression") {
return;
}
if (node.init.id && node.id.name !== node.init.id.name) {
report(node, node.id.name, node.init.id.name, false);
}
},
AssignmentExpression(node) {
if (node.right.type !== "FunctionExpression" ||
(node.left.computed && node.left.property.type !== "Literal") ||
(!includeModuleExports && isModuleExports(node.left))
) {
return;
}
const isProp = node.left.type === "MemberExpression" ? true : false;
const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
if (node.right.id && isIdentifier(name) && name !== node.right.id.name) {
report(node, name, node.right.id.name, isProp);
}
},
Property(node) {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") {
return;
}
if (node.key.type === "Identifier" && node.key.name !== node.value.id.name) {
report(node, node.key.name, node.value.id.name, true);
} else if (node.key.type === "Literal" &&
isIdentifier(node.key.value, ecmaVersion) &&
node.key.value !== node.value.id.name
) {
report(node, node.key.value, node.value.id.name, true);
}
}
};
}
};

4
tools/eslint/lib/rules/id-length.js

@ -61,7 +61,9 @@ module.exports = {
return !parent.computed && ( return !parent.computed && (
// regular property assignment // regular property assignment
(parent.parent.left === parent || // or the last identifier in an ObjectPattern destructuring (parent.parent.left === parent && parent.parent.type === "AssignmentExpression" ||
// or the last identifier in an ObjectPattern destructuring
parent.parent.type === "Property" && parent.parent.value === parent && parent.parent.type === "Property" && parent.parent.value === parent &&
parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent) parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent)
); );

216
tools/eslint/lib/rules/indent.js

@ -121,8 +121,6 @@ module.exports = {
}, },
create(context) { create(context) {
const MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.";
const DEFAULT_VARIABLE_INDENT = 1; const DEFAULT_VARIABLE_INDENT = 1;
const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config
const DEFAULT_FUNCTION_BODY_INDENT = 1; const DEFAULT_FUNCTION_BODY_INDENT = 1;
@ -192,92 +190,67 @@ module.exports = {
} }
} }
const indentPattern = {
normal: indentType === "space" ? /^ +/ : /^\t+/,
excludeCommas: indentType === "space" ? /^[ ,]+/ : /^[\t,]+/
};
const caseIndentStore = {}; const caseIndentStore = {};
/** /**
* Reports a given indent violation and properly pluralizes the message * Creates an error message for a line, given the expected/actual indentation.
* @param {int} expectedAmount The expected amount of indentation characters for this line
* @param {int} actualSpaces The actual number of indentation spaces that were found on this line
* @param {int} actualTabs The actual number of indentation tabs that were found on this line
* @returns {string} An error message for this line
*/
function createErrorMessage(expectedAmount, actualSpaces, actualTabs) {
const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
let foundStatement;
if (actualSpaces > 0 && actualTabs > 0) {
foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs"
} else if (actualSpaces > 0) {
// Abbreviate the message if the expected indentation is also spaces.
// e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
} else if (actualTabs > 0) {
foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
} else {
foundStatement = "0";
}
return `Expected indentation of ${expectedStatement} but found ${foundStatement}.`;
}
/**
* Reports a given indent violation
* @param {ASTNode} node Node violating the indent rule * @param {ASTNode} node Node violating the indent rule
* @param {int} needed Expected indentation character count * @param {int} needed Expected indentation character count
* @param {int} gotten Indentation character count in the actual node/code * @param {int} gottenSpaces Indentation space count in the actual node/code
* @param {int} gottenTabs Indentation tab count in the actual node/code
* @param {Object=} loc Error line and column location * @param {Object=} loc Error line and column location
* @param {boolean} isLastNodeCheck Is the error for last node check * @param {boolean} isLastNodeCheck Is the error for last node check
* @returns {void} * @returns {void}
*/ */
function report(node, needed, gotten, loc, isLastNodeCheck) { function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
const msgContext = {
needed,
type: indentType,
characters: needed === 1 ? "character" : "characters",
gotten
};
const indentChar = indentType === "space" ? " " : "\t";
/**
* Responsible for fixing the indentation issue fix
* @returns {Function} function to be executed by the fixer
* @private
*/
function getFixerFunction() {
let rangeToFix = [];
if (needed > gotten) { if (gottenSpaces && gottenTabs) {
const spaces = indentChar.repeat(needed - gotten);
if (isLastNodeCheck === true) { // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
rangeToFix = [ return;
node.range[1] - 1, }
node.range[1] - 1
];
} else {
rangeToFix = [
node.range[0],
node.range[0]
];
}
return function(fixer) { const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
return fixer.insertTextBeforeRange(rangeToFix, spaces);
};
} else {
if (isLastNodeCheck === true) {
rangeToFix = [
node.range[1] - (gotten - needed) - 1,
node.range[1] - 1
];
} else {
rangeToFix = [
node.range[0] - (gotten - needed),
node.range[0]
];
}
return function(fixer) { const textRange = isLastNodeCheck
return fixer.removeRange(rangeToFix); ? [node.range[1] - gottenSpaces - gottenTabs - 1, node.range[1] - 1]
}; : [node.range[0] - gottenSpaces - gottenTabs, node.range[0]];
}
}
if (loc) { context.report({
context.report({ node,
node, loc,
loc, message: createErrorMessage(needed, gottenSpaces, gottenTabs),
message: MESSAGE, fix: fixer => fixer.replaceTextRange(textRange, desiredIndent)
data: msgContext, });
fix: getFixerFunction()
});
} else {
context.report({
node,
message: MESSAGE,
data: msgContext,
fix: getFixerFunction()
});
}
} }
/** /**
@ -285,15 +258,23 @@ module.exports = {
* @param {ASTNode|Token} node Node to examine * @param {ASTNode|Token} node Node to examine
* @param {boolean} [byLastLine=false] get indent of node's last line * @param {boolean} [byLastLine=false] get indent of node's last line
* @param {boolean} [excludeCommas=false] skip comma on start of line * @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {int} Indent * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also
contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
`badChar` is the amount of the other indentation character.
*/ */
function getNodeIndent(node, byLastLine, excludeCommas) { function getNodeIndent(node, byLastLine) {
const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node); const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
const src = sourceCode.getText(token, token.loc.start.column); const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split("");
const regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal; const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t"));
const indent = regExp.exec(src); const spaces = indentChars.filter(char => char === " ").length;
const tabs = indentChars.filter(char => char === "\t").length;
return indent ? indent[0].length : 0;
return {
space: spaces,
tab: tabs,
goodChar: indentType === "space" ? spaces : tabs,
badChar: indentType === "space" ? tabs : spaces
};
} }
/** /**
@ -313,27 +294,29 @@ module.exports = {
/** /**
* Check indent for node * Check indent for node
* @param {ASTNode} node Node to check * @param {ASTNode} node Node to check
* @param {int} indent needed indent * @param {int} neededIndent needed indent
* @param {boolean} [excludeCommas=false] skip comma on start of line * @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {void} * @returns {void}
*/ */
function checkNodeIndent(node, indent, excludeCommas) { function checkNodeIndent(node, neededIndent) {
const nodeIndent = getNodeIndent(node, false, excludeCommas); const actualIndent = getNodeIndent(node, false);
if ( if (
node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && node.type !== "ArrayExpression" &&
nodeIndent !== indent && isNodeFirstInLine(node) node.type !== "ObjectExpression" &&
(actualIndent.goodChar !== neededIndent || actualIndent.badChar !== 0) &&
isNodeFirstInLine(node)
) { ) {
report(node, indent, nodeIndent); report(node, neededIndent, actualIndent.space, actualIndent.tab);
} }
if (node.type === "IfStatement" && node.alternate) { if (node.type === "IfStatement" && node.alternate) {
const elseToken = sourceCode.getTokenBefore(node.alternate); const elseToken = sourceCode.getTokenBefore(node.alternate);
checkNodeIndent(elseToken, indent, excludeCommas); checkNodeIndent(elseToken, neededIndent);
if (!isNodeFirstInLine(node.alternate)) { if (!isNodeFirstInLine(node.alternate)) {
checkNodeIndent(node.alternate, indent, excludeCommas); checkNodeIndent(node.alternate, neededIndent);
} }
} }
} }
@ -345,8 +328,8 @@ module.exports = {
* @param {boolean} [excludeCommas=false] skip comma on start of line * @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {void} * @returns {void}
*/ */
function checkNodesIndent(nodes, indent, excludeCommas) { function checkNodesIndent(nodes, indent) {
nodes.forEach(node => checkNodeIndent(node, indent, excludeCommas)); nodes.forEach(node => checkNodeIndent(node, indent));
} }
/** /**
@ -359,11 +342,12 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node); const lastToken = sourceCode.getLastToken(node);
const endIndent = getNodeIndent(lastToken, true); const endIndent = getNodeIndent(lastToken, true);
if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) { if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) {
report( report(
node, node,
lastLineIndent, lastLineIndent,
endIndent, endIndent.space,
endIndent.tab,
{ line: lastToken.loc.start.line, column: lastToken.loc.start.column }, { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
true true
); );
@ -379,11 +363,12 @@ module.exports = {
function checkFirstNodeLineIndent(node, firstLineIndent) { function checkFirstNodeLineIndent(node, firstLineIndent) {
const startIndent = getNodeIndent(node, false); const startIndent = getNodeIndent(node, false);
if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) { if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) {
report( report(
node, node,
firstLineIndent, firstLineIndent,
startIndent, startIndent.space,
startIndent.tab,
{ line: node.loc.start.line, column: node.loc.start.column } { line: node.loc.start.line, column: node.loc.start.column }
); );
} }
@ -526,11 +511,11 @@ module.exports = {
calleeNode.parent.type === "ArrayExpression")) { calleeNode.parent.type === "ArrayExpression")) {
// If function is part of array or object, comma can be put at left // If function is part of array or object, comma can be put at left
indent = getNodeIndent(calleeNode, false, false); indent = getNodeIndent(calleeNode, false, false).goodChar;
} else { } else {
// If function is standalone, simple calculate indent // If function is standalone, simple calculate indent
indent = getNodeIndent(calleeNode); indent = getNodeIndent(calleeNode).goodChar;
} }
if (calleeNode.parent.type === "CallExpression") { if (calleeNode.parent.type === "CallExpression") {
@ -538,13 +523,13 @@ module.exports = {
if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") { if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") {
if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) { if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) {
indent = getNodeIndent(calleeParent); indent = getNodeIndent(calleeParent).goodChar;
} }
} else { } else {
if (isArgBeforeCalleeNodeMultiline(calleeNode) && if (isArgBeforeCalleeNodeMultiline(calleeNode) &&
calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line && calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line &&
!isNodeFirstInLine(calleeNode)) { !isNodeFirstInLine(calleeNode)) {
indent = getNodeIndent(calleeParent); indent = getNodeIndent(calleeParent).goodChar;
} }
} }
} }
@ -644,7 +629,7 @@ module.exports = {
effectiveParent = parent.parent; effectiveParent = parent.parent;
} }
} }
nodeIndent = getNodeIndent(effectiveParent); nodeIndent = getNodeIndent(effectiveParent).goodChar;
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) { if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) { if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) {
@ -654,7 +639,8 @@ module.exports = {
parent.type === "ArrayExpression" || parent.type === "ArrayExpression" ||
parent.type === "CallExpression" || parent.type === "CallExpression" ||
parent.type === "ArrowFunctionExpression" || parent.type === "ArrowFunctionExpression" ||
parent.type === "NewExpression" parent.type === "NewExpression" ||
parent.type === "LogicalExpression"
) { ) {
nodeIndent = nodeIndent + indentSize; nodeIndent = nodeIndent + indentSize;
} }
@ -667,7 +653,7 @@ module.exports = {
checkFirstNodeLineIndent(node, nodeIndent); checkFirstNodeLineIndent(node, nodeIndent);
} else { } else {
nodeIndent = getNodeIndent(node); nodeIndent = getNodeIndent(node).goodChar;
elementsIndent = nodeIndent + indentSize; elementsIndent = nodeIndent + indentSize;
} }
@ -679,8 +665,7 @@ module.exports = {
elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
} }
// Comma can be placed before property name checkNodesIndent(elements, elementsIndent);
checkNodesIndent(elements, elementsIndent, true);
if (elements.length > 0) { if (elements.length > 0) {
@ -736,9 +721,9 @@ module.exports = {
]; ];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) { if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
indent = getNodeIndent(node.parent); indent = getNodeIndent(node.parent).goodChar;
} else { } else {
indent = getNodeIndent(node); indent = getNodeIndent(node).goodChar;
} }
if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") { if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") {
@ -784,13 +769,12 @@ module.exports = {
*/ */
function checkIndentInVariableDeclarations(node) { function checkIndentInVariableDeclarations(node) {
const elements = filterOutSameLineVars(node); const elements = filterOutSameLineVars(node);
const nodeIndent = getNodeIndent(node); const nodeIndent = getNodeIndent(node).goodChar;
const lastElement = elements[elements.length - 1]; const lastElement = elements[elements.length - 1];
const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind];
// Comma can be placed before declaration checkNodesIndent(elements, elementsIndent);
checkNodesIndent(elements, elementsIndent, true);
// Only check the last line if there is any token after the last item // Only check the last line if there is any token after the last item
if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) { if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
@ -802,7 +786,7 @@ module.exports = {
if (tokenBeforeLastElement.value === ",") { if (tokenBeforeLastElement.value === ",") {
// Special case for comma-first syntax where the semicolon is indented // Special case for comma-first syntax where the semicolon is indented
checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement)); checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar);
} else { } else {
checkLastNodeLineIndent(node, elementsIndent - indentSize); checkLastNodeLineIndent(node, elementsIndent - indentSize);
} }
@ -834,7 +818,7 @@ module.exports = {
return caseIndentStore[switchNode.loc.start.line]; return caseIndentStore[switchNode.loc.start.line];
} else { } else {
if (typeof switchIndent === "undefined") { if (typeof switchIndent === "undefined") {
switchIndent = getNodeIndent(switchNode); switchIndent = getNodeIndent(switchNode).goodChar;
} }
if (switchNode.cases.length > 0 && options.SwitchCase === 0) { if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
@ -853,7 +837,7 @@ module.exports = {
if (node.body.length > 0) { if (node.body.length > 0) {
// Root nodes should have no indent // Root nodes should have no indent
checkNodesIndent(node.body, getNodeIndent(node)); checkNodesIndent(node.body, getNodeIndent(node).goodChar);
} }
}, },
@ -912,7 +896,7 @@ module.exports = {
return; return;
} }
const propertyIndent = getNodeIndent(node) + indentSize * options.MemberExpression; const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression;
const checkNodes = [node.property]; const checkNodes = [node.property];
@ -928,7 +912,7 @@ module.exports = {
SwitchStatement(node) { SwitchStatement(node) {
// Switch is not a 'BlockStatement' // Switch is not a 'BlockStatement'
const switchIndent = getNodeIndent(node); const switchIndent = getNodeIndent(node).goodChar;
const caseIndent = expectedCaseIndent(node, switchIndent); const caseIndent = expectedCaseIndent(node, switchIndent);
checkNodesIndent(node.cases, caseIndent); checkNodesIndent(node.cases, caseIndent);
@ -955,7 +939,7 @@ module.exports = {
if (options.FunctionDeclaration.parameters === "first" && node.params.length) { if (options.FunctionDeclaration.parameters === "first" && node.params.length) {
checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
} else if (options.FunctionDeclaration.parameters !== null) { } else if (options.FunctionDeclaration.parameters !== null) {
checkNodesIndent(node.params, indentSize * options.FunctionDeclaration.parameters); checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters);
} }
}, },
@ -966,7 +950,7 @@ module.exports = {
if (options.FunctionExpression.parameters === "first" && node.params.length) { if (options.FunctionExpression.parameters === "first" && node.params.length) {
checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
} else if (options.FunctionExpression.parameters !== null) { } else if (options.FunctionExpression.parameters !== null) {
checkNodesIndent(node.params, indentSize * options.FunctionExpression.parameters); checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters);
} }
} }
}; };

2
tools/eslint/lib/rules/init-declarations.js

@ -45,7 +45,7 @@ function isInitialized(node) {
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require or disallow initialization in `var` declarations", description: "require or disallow initialization in variable declarations",
category: "Variables", category: "Variables",
recommended: false recommended: false
}, },

44
tools/eslint/lib/rules/keyword-spacing.js

@ -23,7 +23,7 @@ const NEXT_TOKEN_M = /^[\{*]$/;
const TEMPLATE_OPEN_PAREN = /\$\{$/; const TEMPLATE_OPEN_PAREN = /\$\{$/;
const TEMPLATE_CLOSE_PAREN = /^\}/; const TEMPLATE_CLOSE_PAREN = /^\}/;
const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/; const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/;
const KEYS = keywords.concat(["as", "await", "from", "get", "let", "of", "set", "yield"]); const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]);
// check duplications. // check duplications.
(function() { (function() {
@ -352,6 +352,23 @@ module.exports = {
} }
} }
/**
* Reports `async` or `function` keywords of a given node if usage of
* spacing around those keywords is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForFunction(node) {
const firstToken = node && sourceCode.getFirstToken(node);
if (firstToken &&
(firstToken.type === "Keyword" || firstToken.value === "async")
) {
checkSpacingBefore(firstToken);
}
}
/** /**
* Reports `class` and `extends` keywords of a given node if usage of * Reports `class` and `extends` keywords of a given node if usage of
* spacing around those keywords is invalid. * spacing around those keywords is invalid.
@ -482,7 +499,13 @@ module.exports = {
if (node.static) { if (node.static) {
checkSpacingAroundFirstToken(node); checkSpacingAroundFirstToken(node);
} }
if (node.kind === "get" || node.kind === "set") { if (node.kind === "get" ||
node.kind === "set" ||
(
(node.method || node.type === "MethodDefinition") &&
node.value.async
)
) {
const token = sourceCode.getFirstToken( const token = sourceCode.getFirstToken(
node, node,
node.static ? 1 : 0 node.static ? 1 : 0
@ -492,6 +515,17 @@ module.exports = {
} }
} }
/**
* Reports `await` keyword of a given node if usage of spacing before
* this keyword is invalid.
*
* @param {ASTNode} node - A node to report.
* @returns {void}
*/
function checkSpacingForAwaitExpression(node) {
checkSpacingBefore(sourceCode.getFirstToken(node));
}
return { return {
// Statements // Statements
@ -522,13 +556,15 @@ module.exports = {
ExportNamedDeclaration: checkSpacingForModuleDeclaration, ExportNamedDeclaration: checkSpacingForModuleDeclaration,
ExportDefaultDeclaration: checkSpacingAroundFirstToken, ExportDefaultDeclaration: checkSpacingAroundFirstToken,
ExportAllDeclaration: checkSpacingForModuleDeclaration, ExportAllDeclaration: checkSpacingForModuleDeclaration,
FunctionDeclaration: checkSpacingBeforeFirstToken, FunctionDeclaration: checkSpacingForFunction,
ImportDeclaration: checkSpacingForModuleDeclaration, ImportDeclaration: checkSpacingForModuleDeclaration,
VariableDeclaration: checkSpacingAroundFirstToken, VariableDeclaration: checkSpacingAroundFirstToken,
// Expressions // Expressions
ArrowFunctionExpression: checkSpacingForFunction,
AwaitExpression: checkSpacingForAwaitExpression,
ClassExpression: checkSpacingForClass, ClassExpression: checkSpacingForClass,
FunctionExpression: checkSpacingBeforeFirstToken, FunctionExpression: checkSpacingForFunction,
NewExpression: checkSpacingBeforeFirstToken, NewExpression: checkSpacingBeforeFirstToken,
Super: checkSpacingBeforeFirstToken, Super: checkSpacingBeforeFirstToken,
ThisExpression: checkSpacingBeforeFirstToken, ThisExpression: checkSpacingBeforeFirstToken,

2
tools/eslint/lib/rules/line-comment-position.js

@ -50,7 +50,7 @@ module.exports = {
ignorePattern, ignorePattern,
applyDefaultPatterns = true; applyDefaultPatterns = true;
if (!options || typeof option === "string") { if (!options || typeof options === "string") {
above = !options || options === "above"; above = !options || options === "above";
} else { } else {

9
tools/eslint/lib/rules/lines-around-directive.js

@ -37,7 +37,8 @@ module.exports = {
minProperties: 2 minProperties: 2
} }
] ]
}] }],
fixable: "whitespace"
}, },
create(context) { create(context) {
@ -88,6 +89,12 @@ module.exports = {
expected: expected ? "Expected" : "Unexpected", expected: expected ? "Expected" : "Unexpected",
value: node.expression.value, value: node.expression.value,
location location
},
fix(fixer) {
if (expected) {
return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(node, "\n");
}
return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [node.range[1], node.range[1] + 1]);
} }
}); });
} }

7
tools/eslint/lib/rules/max-len.js

@ -107,7 +107,7 @@ module.exports = {
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
}); });
return line.length + extraCharacterCount; return Array.from(line).length + extraCharacterCount;
} }
// The options object must be the last option specified… // The options object must be the last option specified…
@ -235,8 +235,9 @@ module.exports = {
* @private * @private
*/ */
function groupByLineNumber(acc, node) { function groupByLineNumber(acc, node) {
ensureArrayAndPush(acc, node.loc.start.line, node); for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) {
ensureArrayAndPush(acc, node.loc.end.line, node); ensureArrayAndPush(acc, i, node);
}
return acc; return acc;
} }

2
tools/eslint/lib/rules/max-params.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "enforce a maximum number of parameters in `function` definitions", description: "enforce a maximum number of parameters in function definitions",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

20
tools/eslint/lib/rules/max-statements-per-line.js

@ -22,7 +22,7 @@ module.exports = {
properties: { properties: {
max: { max: {
type: "integer", type: "integer",
minimum: 0 minimum: 1
} }
}, },
additionalProperties: false additionalProperties: false
@ -185,23 +185,7 @@ module.exports = {
"ExportNamedDeclaration:exit": leaveStatement, "ExportNamedDeclaration:exit": leaveStatement,
"ExportDefaultDeclaration:exit": leaveStatement, "ExportDefaultDeclaration:exit": leaveStatement,
"ExportAllDeclaration:exit": leaveStatement, "ExportAllDeclaration:exit": leaveStatement,
"Program:exit": reportFirstExtraStatementAndClear, "Program:exit": reportFirstExtraStatementAndClear
// 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,
message,
data: {
numberOfStatementsOnThisLine: 0,
maxStatementsPerLine,
statements: "statements",
}
});
}
}
}; };
} }
}; };

2
tools/eslint/lib/rules/max-statements.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "enforce a maximum number of statements allowed in `function` blocks", description: "enforce a maximum number of statements allowed in function blocks",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

2
tools/eslint/lib/rules/new-cap.js

@ -75,7 +75,7 @@ function calculateCapIsNewExceptions(config) {
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require constructor `function` names to begin with a capital letter", description: "require constructor names to begin with a capital letter",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

2
tools/eslint/lib/rules/newline-after-var.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require or disallow an empty line after `var` declarations", description: "require or disallow an empty line after variable declarations",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

13
tools/eslint/lib/rules/no-extra-bind.js

@ -22,7 +22,9 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
@ -39,7 +41,14 @@ module.exports = {
context.report({ context.report({
node: node.parent.parent, node: node.parent.parent,
message: "The function binding is unnecessary.", message: "The function binding is unnecessary.",
loc: node.parent.property.loc.start loc: node.parent.property.loc.start,
fix(fixer) {
const firstTokenToRemove = context.getSourceCode()
.getTokensBetween(node.parent.object, node.parent.property)
.find(token => token.value !== ")");
return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
}
}); });
} }

17
tools/eslint/lib/rules/no-extra-parens.js

@ -367,15 +367,15 @@ module.exports = {
* @private * @private
*/ */
function dryBinaryLogical(node) { function dryBinaryLogical(node) {
if (!NESTED_BINARY) { const prec = precedence(node);
const prec = precedence(node); const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
if (hasExcessParens(node.left) && precedence(node.left) >= prec) { if (!shouldSkipLeft && hasExcessParens(node.left) && precedence(node.left) >= prec) {
report(node.left); report(node.left);
} }
if (hasExcessParens(node.right) && precedence(node.right) > prec) { if (!shouldSkipRight && hasExcessParens(node.right) && precedence(node.right) > prec) {
report(node.right); report(node.right);
}
} }
} }
@ -587,6 +587,7 @@ module.exports = {
UnaryExpression: dryUnaryUpdate, UnaryExpression: dryUnaryUpdate,
UpdateExpression: dryUnaryUpdate, UpdateExpression: dryUnaryUpdate,
AwaitExpression: dryUnaryUpdate,
VariableDeclarator(node) { VariableDeclarator(node) {
if (node.init && hasExcessParens(node.init) && if (node.init && hasExcessParens(node.init) &&

35
tools/eslint/lib/rules/no-implicit-coercion.js

@ -107,16 +107,6 @@ function getNonNumericOperand(node) {
return null; return null;
} }
/**
* Checks whether a node is a string literal or not.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the passed in node is a
* string literal or not.
*/
function isStringLiteral(node) {
return astUtils.isStringLiteral(node) && node.type !== "TemplateLiteral";
}
/** /**
* Checks whether a node is an empty string literal or not. * Checks whether a node is an empty string literal or not.
* @param {ASTNode} node The node to check. * @param {ASTNode} node The node to check.
@ -124,7 +114,7 @@ function isStringLiteral(node) {
* empty string literal or not. * empty string literal or not.
*/ */
function isEmptyString(node) { function isEmptyString(node) {
return isStringLiteral(node) && node.value === ""; return astUtils.isStringLiteral(node) && (node.value === "" || (node.type === "TemplateLiteral" && node.quasis.length === 1 && node.quasis[0].value.cooked === ""));
} }
/** /**
@ -134,8 +124,8 @@ function isEmptyString(node) {
*/ */
function isConcatWithEmptyString(node) { function isConcatWithEmptyString(node) {
return node.operator === "+" && ( return node.operator === "+" && (
(isEmptyString(node.left) && !isStringLiteral(node.right)) || (isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) ||
(isEmptyString(node.right) && !isStringLiteral(node.left)) (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
); );
} }
@ -202,19 +192,24 @@ module.exports = {
* Reports an error and autofixes the node * Reports an error and autofixes the node
* @param {ASTNode} node - An ast node to report the error on. * @param {ASTNode} node - An ast node to report the error on.
* @param {string} recommendation - The recommended code for the issue * @param {string} recommendation - The recommended code for the issue
* @param {bool} shouldFix - Whether this report should fix the node
* @returns {void} * @returns {void}
*/ */
function report(node, recommendation) { function report(node, recommendation, shouldFix) {
context.report({ shouldFix = typeof shouldFix === "undefined" ? true : shouldFix;
const reportObj = {
node, node,
message: "use `{{recommendation}}` instead.", message: "use `{{recommendation}}` instead.",
data: { data: {
recommendation recommendation
},
fix(fixer) {
return fixer.replaceText(node, recommendation);
} }
}); };
if (shouldFix) {
reportObj.fix = fixer => fixer.replaceText(node, recommendation);
}
context.report(reportObj);
} }
return { return {
@ -234,7 +229,7 @@ module.exports = {
if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
const recommendation = `${sourceCode.getText(node.argument)} !== -1`; const recommendation = `${sourceCode.getText(node.argument)} !== -1`;
report(node, recommendation); report(node, recommendation, false);
} }
// +foo // +foo

2
tools/eslint/lib/rules/no-implicit-globals.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow `var` and named `function` declarations in the global scope", description: "disallow variable and `function` declarations in the global scope",
category: "Best Practices", category: "Best Practices",
recommended: false recommended: false
}, },

2
tools/eslint/lib/rules/no-inner-declarations.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow `function` or `var` declarations in nested blocks", description: "disallow variable or `function` declarations in nested blocks",
category: "Possible Errors", category: "Possible Errors",
recommended: true recommended: true
}, },

46
tools/eslint/lib/rules/no-lonely-if.js

@ -16,10 +16,13 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
const sourceCode = context.getSourceCode();
return { return {
IfStatement(node) { IfStatement(node) {
@ -31,7 +34,46 @@ module.exports = {
parent.body.length === 1 && grandparent && parent.body.length === 1 && grandparent &&
grandparent.type === "IfStatement" && grandparent.type === "IfStatement" &&
parent === grandparent.alternate) { parent === grandparent.alternate) {
context.report(node, "Unexpected if as the only statement in an else block."); context.report({
node,
message: "Unexpected if as the only statement in an else block.",
fix(fixer) {
const openingElseCurly = sourceCode.getFirstToken(parent);
const closingElseCurly = sourceCode.getLastToken(parent);
const elseKeyword = sourceCode.getTokenBefore(openingElseCurly);
const tokenAfterElseBlock = sourceCode.getTokenAfter(closingElseCurly);
const lastIfToken = sourceCode.getLastToken(node.consequent);
const sourceText = sourceCode.getText();
if (sourceText.slice(openingElseCurly.range[1], node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
// Don't fix if there are any non-whitespace characters interfering (e.g. comments)
return null;
}
if (
node.consequent.type !== "BlockStatement" && lastIfToken.value !== ";" && tokenAfterElseBlock &&
(
node.consequent.loc.end.line === tokenAfterElseBlock.loc.start.line ||
/^[(\[\/+`-]/.test(tokenAfterElseBlock.value) ||
lastIfToken.value === "++" ||
lastIfToken.value === "--"
)
) {
/*
* If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing
* the issue would not change semantics due to ASI. If this would happen, don't do a fix.
*/
return null;
}
return fixer.replaceTextRange(
[openingElseCurly.range[0], closingElseCurly.range[1]],
(elseKeyword.range[1] === openingElseCurly.range[0] ? " " : "") + sourceCode.getText(node)
);
}
});
} }
} }
}; };

2
tools/eslint/lib/rules/no-mixed-requires.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow `require` calls to be mixed with regular `var` declarations", description: "disallow `require` calls to be mixed with regular variable declarations",
category: "Node.js and CommonJS", category: "Node.js and CommonJS",
recommended: false recommended: false
}, },

180
tools/eslint/lib/rules/no-multiple-empty-lines.js

@ -46,11 +46,8 @@ module.exports = {
// Use options.max or 2 as default // Use options.max or 2 as default
let max = 2, let max = 2,
maxEOF, maxEOF = max,
maxBOF; maxBOF = max;
// store lines that appear empty but really aren't
const notEmpty = [];
if (context.options.length) { if (context.options.length) {
max = context.options[0].max; max = context.options[0].max;
@ -59,159 +56,64 @@ module.exports = {
} }
const sourceCode = context.getSourceCode(); const sourceCode = context.getSourceCode();
const fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [];
const lineStartLocations = fullLines.reduce((startIndices, nextLine) => startIndices.concat(startIndices[startIndices.length - 1] + nextLine.length), [0]);
// Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue
const allLines = sourceCode.lines[sourceCode.lines.length - 1] === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines;
const templateLiteralLines = new Set();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
TemplateLiteral(node) { TemplateLiteral(node) {
let start = node.loc.start.line; node.quasis.forEach(literalPart => {
const end = node.loc.end.line;
while (start <= end) { // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines.
notEmpty.push(start); for (let ignoredLine = literalPart.loc.start.line; ignoredLine < literalPart.loc.end.line; ignoredLine++) {
start++; templateLiteralLines.add(ignoredLine);
}
},
"Program:exit": function checkBlankLines(node) {
const lines = sourceCode.lines,
fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [],
linesRangeStart = [];
let firstNonBlankLine = -1,
trimmedLines = [],
blankCounter = 0,
currentLocation,
lastLocation,
firstOfEndingBlankLines,
diff,
rangeStart,
rangeEnd;
/**
* Fix code.
* @param {RuleFixer} fixer - The fixer of this context.
* @returns {Object} The fixing information.
*/
function fix(fixer) {
return fixer.removeRange([rangeStart, rangeEnd]);
}
linesRangeStart.push(0);
lines.forEach(function(str, i) {
const 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);
});
// add the notEmpty lines in there with a placeholder
notEmpty.forEach(function(x, i) {
trimmedLines[i] = x;
}); });
},
"Program:exit"(node) {
return allLines
if (typeof maxEOF === "undefined") { // Given a list of lines, first get a list of line numbers that are non-empty.
.reduce((nonEmptyLineNumbers, line, index) => nonEmptyLineNumbers.concat(line.trim() || templateLiteralLines.has(index + 1) ? [index + 1] : []), [])
/* // Add a value at the end to allow trailing empty lines to be checked.
* Swallow the final newline, as some editors add it .concat(allLines.length + 1)
* automatically and we don't want it to cause an issue
*/
if (trimmedLines[trimmedLines.length - 1] === "") {
trimmedLines = trimmedLines.slice(0, -1);
}
firstOfEndingBlankLines = trimmedLines.length; // Given two line numbers of non-empty lines, report the lines between if the difference is too large.
} else { .reduce((lastLineNumber, lineNumber) => {
let message, maxAllowed;
// save the number of the first of the last blank lines if (lastLineNumber === 0) {
firstOfEndingBlankLines = trimmedLines.length; message = "Too many blank lines at the beginning of file. Max of {{max}} allowed.";
while (trimmedLines[firstOfEndingBlankLines - 1] === "" maxAllowed = maxBOF;
&& firstOfEndingBlankLines > 0) { } else if (lineNumber === allLines.length + 1) {
firstOfEndingBlankLines--; message = "Too many blank lines at the end of file. Max of {{max}} allowed.";
} maxAllowed = maxEOF;
}
// Aggregate and count blank lines
if (firstNonBlankLine > maxBOF) {
diff = firstNonBlankLine - maxBOF;
rangeStart = linesRangeStart[firstNonBlankLine - diff];
rangeEnd = linesRangeStart[firstNonBlankLine];
context.report({
node,
loc: node.loc.start,
message: "Too many blank lines at the beginning of file. Max of {{maxBOF}} allowed.",
data: {
maxBOF
},
fix
});
}
currentLocation = firstNonBlankLine - 1;
lastLocation = currentLocation;
currentLocation = trimmedLines.indexOf("", currentLocation + 1);
while (currentLocation !== -1) {
lastLocation = currentLocation;
currentLocation = trimmedLines.indexOf("", currentLocation + 1);
if (lastLocation === currentLocation - 1) {
blankCounter++;
} else {
const location = {
line: lastLocation + 1,
column: 0
};
if (lastLocation < firstOfEndingBlankLines) {
// 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,
loc: location,
message: "More than {{max}} blank {{lines}} not allowed.",
data: {
max,
lines: (max === 1 ? "line" : "lines")
},
fix
});
}
} else { } else {
message = "More than {{max}} blank {{pluralizedLines}} not allowed.";
maxAllowed = max;
}
// inside the last blank lines if (lineNumber - lastLineNumber - 1 > maxAllowed) {
if (blankCounter > maxEOF) { context.report({
diff = blankCounter - maxEOF + 1; node,
rangeStart = linesRangeStart[location.line - diff]; loc: {start: {line: lastLineNumber + 1, column: 0}, end: {line: lineNumber, column: 0}},
rangeEnd = linesRangeStart[location.line - 1]; message,
context.report({ data: {max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines"},
node, fix: fixer => fixer.removeRange([lineStartLocations[lastLineNumber], lineStartLocations[lineNumber - maxAllowed - 1]])
loc: location, });
message: "Too many blank lines at the end of file. Max of {{maxEOF}} allowed.",
data: {
maxEOF
},
fix
});
}
} }
// Finally, reset the blank counter return lineNumber;
blankCounter = 0; }, 0);
}
}
} }
}; };
} }
}; };

2
tools/eslint/lib/rules/no-redeclare.js

@ -12,7 +12,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow `var` redeclaration", description: "disallow variable redeclaration",
category: "Best Practices", category: "Best Practices",
recommended: true recommended: true
}, },

30
tools/eslint/lib/rules/no-regex-spaces.js

@ -5,6 +5,8 @@
"use strict"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -17,7 +19,9 @@ module.exports = {
recommended: true recommended: true
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
@ -27,19 +31,27 @@ module.exports = {
* Validate regular expressions * Validate regular expressions
* @param {ASTNode} node node to validate * @param {ASTNode} node node to validate
* @param {string} value regular expression to validate * @param {string} value regular expression to validate
* @param {number} valueStart The start location of the regex/string literal. It will always be the case that
`sourceCode.getText().slice(valueStart, valueStart + value.length) === value`
* @returns {void} * @returns {void}
* @private * @private
*/ */
function checkRegex(node, value) { function checkRegex(node, value, valueStart) {
const multipleSpacesRegex = /( {2,})+?/, const multipleSpacesRegex = /( {2,})+?/,
regexResults = multipleSpacesRegex.exec(value); regexResults = multipleSpacesRegex.exec(value);
if (regexResults !== null) { if (regexResults !== null) {
const count = regexResults[0].length;
context.report({ context.report({
node, node,
message: "Spaces are hard to count. Use {{{count}}}.", message: "Spaces are hard to count. Use {{{count}}}.",
data: { data: {count},
count: regexResults[0].length fix(fixer) {
return fixer.replaceTextRange(
[valueStart + regexResults.index, valueStart + regexResults.index + count],
` {${count}}`
);
} }
}); });
@ -62,7 +74,7 @@ module.exports = {
nodeValue = token.value; nodeValue = token.value;
if (nodeType === "RegularExpression") { if (nodeType === "RegularExpression") {
checkRegex(node, nodeValue); checkRegex(node, nodeValue, token.start);
} }
} }
@ -83,8 +95,12 @@ module.exports = {
* @private * @private
*/ */
function checkFunction(node) { function checkFunction(node) {
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) { const scope = context.getScope();
checkRegex(node, node.arguments[0].value); const regExpVar = astUtils.getVariableByName(scope, "RegExp");
const shadowed = regExpVar && regExpVar.defs.length > 0;
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0]) && !shadowed) {
checkRegex(node, node.arguments[0].value, node.arguments[0].start + 1);
} }
} }

149
tools/eslint/lib/rules/no-restricted-properties.js

@ -15,29 +15,46 @@ module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow certain properties on certain objects", description: "disallow certain properties on certain objects",
category: "Node.js and CommonJS", category: "Best Practices",
recommended: false recommended: false
}, },
schema: { schema: {
type: "array", type: "array",
items: { items: {
type: "object", anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided.
properties: { {
object: { type: "object",
type: "string" properties: {
object: {
type: "string"
},
property: {
type: "string"
},
message: {
type: "string"
}
},
additionalProperties: false,
required: ["object"]
}, },
property: { {
type: "string" type: "object",
}, properties: {
message: { object: {
type: "string" type: "string"
},
property: {
type: "string"
},
message: {
type: "string"
}
},
additionalProperties: false,
required: ["property"]
} }
},
additionalProperties: false,
required: [
"object",
"property"
] ]
}, },
uniqueItems: true uniqueItems: true
@ -51,38 +68,96 @@ module.exports = {
return {}; return {};
} }
const restrictedProperties = restrictedCalls.reduce(function(restrictions, option) { const restrictedProperties = new Map();
const globallyRestrictedObjects = new Map();
const globallyRestrictedProperties = new Map();
restrictedCalls.forEach(option => {
const objectName = option.object; const objectName = option.object;
const propertyName = option.property; const propertyName = option.property;
if (!restrictions.has(objectName)) { if (typeof objectName === "undefined") {
restrictions.set(objectName, new Map()); globallyRestrictedProperties.set(propertyName, {message: option.message});
} else if (typeof propertyName === "undefined") {
globallyRestrictedObjects.set(objectName, {message: option.message});
} else {
if (!restrictedProperties.has(objectName)) {
restrictedProperties.set(objectName, new Map());
}
restrictedProperties.get(objectName).set(propertyName, {
message: option.message
});
} }
});
restrictions.get(objectName).set(propertyName, { /**
message: option.message * Checks to see whether a property access is restricted, and reports it if so.
}); * @param {ASTNode} node The node to report
* @param {string} objectName The name of the object
* @param {string} propertyName The name of the property
* @returns {undefined}
*/
function checkPropertyAccess(node, objectName, propertyName) {
if (propertyName === null) {
return;
}
const matchedObject = restrictedProperties.get(objectName);
const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName);
const globalMatchedProperty = globallyRestrictedProperties.get(propertyName);
return restrictions; if (matchedObjectProperty) {
}, new Map()); const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
return { context.report(node, "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", {
MemberExpression(node) { objectName,
const objectName = node.object && node.object.name; propertyName,
const propertyName = astUtils.getStaticPropertyName(node); message
const matchedObject = restrictedProperties.get(objectName); });
const matchedObjectProperty = matchedObject && matchedObject.get(propertyName); } else if (globalMatchedProperty) {
const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";
if (matchedObjectProperty) {
const message = matchedObjectProperty.message ? " " + matchedObjectProperty.message : ""; context.report(node, "'{{propertyName}}' is restricted from being used.{{message}}", {
propertyName,
context.report(node, "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", { message
objectName, });
propertyName, }
message }
/**
* Checks property accesses in a destructuring assignment expression, e.g. `var foo; ({foo} = bar);`
* @param {ASTNode} node An AssignmentExpression or AssignmentPattern node
* @returns {undefined}
*/
function checkDestructuringAssignment(node) {
if (node.right.type === "Identifier") {
const objectName = node.right.name;
if (node.left.type === "ObjectPattern") {
node.left.properties.forEach(property => {
checkPropertyAccess(node.left, objectName, astUtils.getStaticPropertyName(property));
}); });
} }
} }
}
return {
MemberExpression(node) {
checkPropertyAccess(node, node.object && node.object.name, astUtils.getStaticPropertyName(node));
},
VariableDeclarator(node) {
if (node.init && node.init.type === "Identifier") {
const objectName = node.init.name;
if (node.id.type === "ObjectPattern") {
node.id.properties.forEach(property => {
checkPropertyAccess(node.id, objectName, astUtils.getStaticPropertyName(property));
});
}
}
},
AssignmentExpression: checkDestructuringAssignment,
AssignmentPattern: checkDestructuringAssignment
}; };
} }
}; };

2
tools/eslint/lib/rules/no-shadow.js

@ -18,7 +18,7 @@ const astUtils = require("../ast-utils");
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow `var` declarations from shadowing variables in the outer scope", description: "disallow variable declarations from shadowing variables declared in the outer scope",
category: "Variables", category: "Variables",
recommended: false recommended: false
}, },

2
tools/eslint/lib/rules/no-spaced-func.js

@ -13,7 +13,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "disallow spacing between `function` identifiers and their applications (deprecated)", description: "disallow spacing between function identifiers and their applications (deprecated)",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false, recommended: false,
replacedBy: ["func-call-spacing"] replacedBy: ["func-call-spacing"]

33
tools/eslint/lib/rules/no-undef-init.js

@ -5,6 +5,8 @@
"use strict"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -17,19 +19,38 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
const sourceCode = context.getSourceCode();
return { return {
VariableDeclarator(node) { VariableDeclarator(node) {
const name = node.id.name, const name = sourceCode.getText(node.id),
init = node.init && node.init.name; init = node.init && node.init.name,
scope = context.getScope(),
if (init === "undefined" && node.parent.kind !== "const") { undefinedVar = astUtils.getVariableByName(scope, "undefined"),
context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name }); shadowed = undefinedVar && undefinedVar.defs.length > 0;
if (init === "undefined" && node.parent.kind !== "const" && !shadowed) {
context.report({
node,
message: "It's not necessary to initialize '{{name}}' to undefined.",
data: {name},
fix(fixer) {
if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") {
// Don't fix destructuring assignment to `undefined`.
return null;
}
return fixer.removeRange([node.id.range[1], node.range[1]]);
}
});
} }
} }
}; };

2
tools/eslint/lib/rules/no-unused-expressions.js

@ -101,7 +101,7 @@ module.exports = {
} }
} }
return /^(?:Assignment|Call|New|Update|Yield)Expression$/.test(node.type) || return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/.test(node.type) ||
(node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0); (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
} }

38
tools/eslint/lib/rules/no-unused-vars.js

@ -60,7 +60,8 @@ module.exports = {
create(context) { create(context) {
const MESSAGE = "'{{name}}' is defined but never used."; const DEFINED_MESSAGE = "'{{name}}' is defined but never used.";
const ASSIGNED_MESSAGE = "'{{name}}' is assigned a value but never used.";
const config = { const config = {
vars: "all", vars: "all",
@ -414,6 +415,33 @@ module.exports = {
}); });
} }
/**
* Checks whether the given variable is the last parameter in the non-ignored parameters.
*
* @param {escope.Variable} variable - The variable to check.
* @returns {boolean} `true` if the variable is the last.
*/
function isLastInNonIgnoredParameters(variable) {
const def = variable.defs[0];
// This is the last.
if (def.index === def.node.params.length - 1) {
return true;
}
// if all parameters preceded by this variable are ignored and unused, this is the last.
if (config.argsIgnorePattern) {
const params = context.getDeclaredVariables(def.node);
const posteriorParams = params.slice(params.indexOf(variable) + 1);
if (posteriorParams.every(v => v.references.length === 0 && config.argsIgnorePattern.test(v.name))) {
return true;
}
}
return false;
}
/** /**
* Gets an array of variables without read references. * Gets an array of variables without read references.
* @param {Scope} scope - an escope Scope object. * @param {Scope} scope - an escope Scope object.
@ -466,7 +494,7 @@ module.exports = {
if (type === "Parameter") { if (type === "Parameter") {
// skip any setter argument // skip any setter argument
if (def.node.parent.type === "Property" && def.node.parent.kind === "set") { if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
continue; continue;
} }
@ -481,7 +509,7 @@ module.exports = {
} }
// if "args" option is "after-used", skip all but the last parameter // if "args" option is "after-used", skip all but the last parameter
if (config.args === "after-used" && def.index < def.node.params.length - 1) { if (config.args === "after-used" && !isLastInNonIgnoredParameters(variable)) {
continue; continue;
} }
} else { } else {
@ -569,13 +597,13 @@ module.exports = {
context.report({ context.report({
node: programNode, node: programNode,
loc: getLocation(unusedVar), loc: getLocation(unusedVar),
message: MESSAGE, message: DEFINED_MESSAGE,
data: unusedVar data: unusedVar
}); });
} else if (unusedVar.defs.length > 0) { } else if (unusedVar.defs.length > 0) {
context.report({ context.report({
node: unusedVar.identifiers[0], node: unusedVar.identifiers[0],
message: MESSAGE, message: unusedVar.references.some(ref => ref.isWrite()) ? ASSIGNED_MESSAGE : DEFINED_MESSAGE,
data: unusedVar data: unusedVar
}); });
} }

23
tools/eslint/lib/rules/no-useless-computed-key.js

@ -18,7 +18,9 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
const sourceCode = context.getSourceCode(); const sourceCode = context.getSourceCode();
@ -33,7 +35,24 @@ module.exports = {
nodeType = typeof key.value; nodeType = typeof key.value;
if (key.type === "Literal" && (nodeType === "string" || nodeType === "number")) { if (key.type === "Literal" && (nodeType === "string" || nodeType === "number")) {
context.report(node, MESSAGE_UNNECESSARY_COMPUTED, { property: sourceCode.getText(key) }); context.report({
node,
message: MESSAGE_UNNECESSARY_COMPUTED,
data: { property: sourceCode.getText(key) },
fix(fixer) {
const leftSquareBracket = sourceCode.getFirstToken(node, node.value.generator || node.value.async ? 1 : 0);
const rightSquareBracket = sourceCode.getTokensBetween(node.key, node.value).find(token => token.value === "]");
const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
if (tokensBetween.slice(0, -1).some((token, index) => sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
// If there are comments between the brackets and the property name, don't do a fix.
return null;
}
return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], key.raw);
}
});
} }
} }
}; };

53
tools/eslint/lib/rules/no-useless-escape.js

@ -76,23 +76,44 @@ module.exports = {
* @private * @private
* @param {string[]} escapes - list of valid escapes * @param {string[]} escapes - list of valid escapes
* @param {ASTNode} node - node to validate. * @param {ASTNode} node - node to validate.
* @param {string} elm - string slice to validate. * @param {string} match - string slice to validate.
* @returns {void} * @returns {void}
*/ */
function validate(escapes, node, elm) { function validate(escapes, node, match) {
const escapeNotFound = escapes.indexOf(elm[0][1]) === -1; const isTemplateElement = node.type === "TemplateElement";
const isQuoteEscape = elm[0][1] === node.raw[0]; const escapedChar = match[0][1];
let isUnnecessaryEscape = escapes.indexOf(escapedChar) === -1;
let isQuoteEscape;
if (escapeNotFound && !isQuoteEscape) { if (isTemplateElement) {
isQuoteEscape = escapedChar === "`";
if (escapedChar === "$") {
// Warn if `\$` is not followed by `{`
isUnnecessaryEscape = match.input[match.index + 2] !== "{";
} else if (escapedChar === "{") {
/* Warn if `\{` is not preceded by `$`. If preceded by `$`, escaping
* is necessary and the rule should not warn. If preceded by `/$`, the rule
* will warn for the `/$` instead, as it is the first unnecessarily escaped character.
*/
isUnnecessaryEscape = match.input[match.index - 1] !== "$";
}
} else {
isQuoteEscape = escapedChar === node.raw[0];
}
if (isUnnecessaryEscape && !isQuoteEscape) {
context.report({ context.report({
node, node,
loc: { loc: {
line: node.loc.start.line, line: node.loc.start.line,
column: node.loc.start.column + elm.index column: node.loc.start.column + match.index
}, },
message: "Unnecessary escape character: {{character}}.", message: "Unnecessary escape character: {{character}}.",
data: { data: {
character: elm[0] character: match[0]
} }
}); });
} }
@ -105,12 +126,18 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function check(node) { function check(node) {
let nodeEscapes, match; const isTemplateElement = node.type === "TemplateElement";
const value = isTemplateElement ? node.value.raw : node.raw;
const pattern = /\\[^\d]/g; const pattern = /\\[^\d]/g;
let nodeEscapes,
match;
if (typeof node.value === "string") { if (typeof node.value === "string" || isTemplateElement) {
// JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/ /*
* JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/.
* In addition, backticks are not supported by JSX yet: https://github.com/facebook/jsx/issues/25.
*/
if (node.parent.type === "JSXAttribute") { if (node.parent.type === "JSXAttribute") {
return; return;
} }
@ -122,12 +149,14 @@ module.exports = {
return; return;
} }
while ((match = pattern.exec(node.raw))) { while ((match = pattern.exec(value))) {
validate(nodeEscapes, node, match); validate(nodeEscapes, node, match);
} }
} }
return { return {
Literal: check Literal: check,
TemplateElement: check
}; };
} }
}; };

6
tools/eslint/lib/rules/no-whitespace-before-property.js

@ -62,6 +62,12 @@ module.exports = {
propName: sourceCode.getText(node.property) propName: sourceCode.getText(node.property)
}, },
fix(fixer) { fix(fixer) {
if (!node.computed && astUtils.isDecimalInteger(node.object)) {
// If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError.
// Don't fix this case.
return null;
}
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText); return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
} }
}); });

37
tools/eslint/lib/rules/object-shorthand.js

@ -14,6 +14,11 @@ const OPTIONS = {
consistentAsNeeded: "consistent-as-needed" consistentAsNeeded: "consistent-as-needed"
}; };
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -113,13 +118,13 @@ module.exports = {
} }
/** /**
* Determines if the property is not a getter and a setter. * Determines if the property can have a shorthand form.
* @param {ASTNode} property Property AST node * @param {ASTNode} property Property AST node
* @returns {boolean} True if the property is not a getter and a setter, false if it is. * @returns {boolean} True if the property can have a shorthand form
* @private * @private
**/ **/
function isNotGetterOrSetter(property) { function canHaveShorthand(property) {
return (property.kind !== "set" && property.kind !== "get"); return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty");
} }
/** /**
@ -149,15 +154,17 @@ module.exports = {
* @returns {boolean} True if the key and value are named equally, false if not. * @returns {boolean} True if the key and value are named equally, false if not.
* @private * @private
**/ **/
function isRedudant(property) { function isRedundant(property) {
return (property.key && ( const value = property.value;
// A function expression if (value.type === "FunctionExpression") {
property.value && property.value.id && property.value.id.name === property.key.name || return !value.id; // Only anonymous should be shorthand method.
}
if (value.type === "Identifier") {
return astUtils.getStaticPropertyName(property) === value.name;
}
// A property return false;
property.value && property.value.name === property.key.name
));
} }
/** /**
@ -168,8 +175,8 @@ module.exports = {
**/ **/
function checkConsistency(node, checkRedundancy) { function checkConsistency(node, checkRedundancy) {
// We are excluding getters and setters as they are considered neither longform nor shorthand. // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand.
const properties = node.properties.filter(isNotGetterOrSetter); const properties = node.properties.filter(canHaveShorthand);
// Do we still have properties left after filtering the getters and setters? // Do we still have properties left after filtering the getters and setters?
if (properties.length > 0) { if (properties.length > 0) {
@ -185,8 +192,8 @@ module.exports = {
} else if (checkRedundancy) { } else if (checkRedundancy) {
// If all properties of the object contain a method or value with a name matching it's key, // If all properties of the object contain a method or value with a name matching it's key,
// all the keys are redudant. // all the keys are redundant.
const canAlwaysUseShorthand = properties.every(isRedudant); const canAlwaysUseShorthand = properties.every(isRedundant);
if (canAlwaysUseShorthand) { if (canAlwaysUseShorthand) {
context.report(node, "Expected shorthand for all properties."); context.report(node, "Expected shorthand for all properties.");

9
tools/eslint/lib/rules/one-var-declaration-per-line.js

@ -11,7 +11,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require or disallow newlines around `var` declarations", description: "require or disallow newlines around variable declarations",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },
@ -20,7 +20,9 @@ module.exports = {
{ {
enum: ["always", "initializations"] enum: ["always", "initializations"]
} }
] ],
fixable: "whitespace"
}, },
create(context) { create(context) {
@ -63,7 +65,8 @@ module.exports = {
context.report({ context.report({
node, node,
message: ERROR_MESSAGE, message: ERROR_MESSAGE,
loc: current.loc.start loc: current.loc.start,
fix: fixer => fixer.insertTextBefore(current, "\n")
}); });
} }
} }

5
tools/eslint/lib/rules/prefer-arrow-callback.js

@ -275,16 +275,17 @@ module.exports = {
const paramsLeftParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1); const paramsLeftParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1);
const paramsRightParen = sourceCode.getTokenBefore(node.body); const paramsRightParen = sourceCode.getTokenBefore(node.body);
const asyncKeyword = node.async ? "async " : "";
const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]); const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
if (callbackInfo.isLexicalThis) { if (callbackInfo.isLexicalThis) {
// If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding. // If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding.
return fixer.replaceText(node.parent.parent, paramsFullText + " => " + sourceCode.getText(node.body)); return fixer.replaceText(node.parent.parent, `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`);
} }
// Otherwise, only replace the `function` keyword and parameters with the arrow function parameters. // Otherwise, only replace the `function` keyword and parameters with the arrow function parameters.
return fixer.replaceTextRange([node.start, node.body.start], paramsFullText + " => "); return fixer.replaceTextRange([node.start, node.body.start], `${asyncKeyword}${paramsFullText} => `);
} }
}); });
} }

21
tools/eslint/lib/rules/prefer-numeric-literals.js

@ -17,7 +17,9 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
@ -27,6 +29,12 @@ module.exports = {
16: "hexadecimal" 16: "hexadecimal"
}; };
const prefixMap = {
2: "0b",
8: "0o",
16: "0x"
};
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -53,6 +61,17 @@ module.exports = {
message: "Use {{radixName}} literals instead of parseInt().", message: "Use {{radixName}} literals instead of parseInt().",
data: { data: {
radixName radixName
},
fix(fixer) {
const newPrefix = prefixMap[node.arguments[1].value];
if (+(newPrefix + node.arguments[0].value) !== parseInt(node.arguments[0].value, node.arguments[1].value)) {
// If the newly-produced literal would be invalid, (e.g. 0b1234),
// or it would yield an incorrect parseInt result for some other reason, don't make a fix.
return null;
}
return fixer.replaceText(node, prefixMap[node.arguments[1].value] + node.arguments[0].value);
} }
}); });
} }

23
tools/eslint/lib/rules/prefer-spread.js

@ -23,7 +23,8 @@ function isVariadicApplyCalling(node) {
node.callee.property.name === "apply" && node.callee.property.name === "apply" &&
node.callee.computed === false && node.callee.computed === false &&
node.arguments.length === 2 && node.arguments.length === 2 &&
node.arguments[1].type !== "ArrayExpression" node.arguments[1].type !== "ArrayExpression" &&
node.arguments[1].type !== "SpreadElement"
); );
} }
@ -78,7 +79,9 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
@ -95,7 +98,21 @@ module.exports = {
const thisArg = node.arguments[0]; const thisArg = node.arguments[0];
if (isValidThisArg(expectedThis, thisArg, sourceCode)) { if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
context.report(node, "use the spread operator instead of the '.apply()'."); context.report({
node,
message: "Use the spread operator instead of '.apply()'.",
fix(fixer) {
if (expectedThis && expectedThis.type !== "Identifier") {
// Don't fix cases where the `this` value could be a computed expression.
return null;
}
const propertyDot = sourceCode.getTokensBetween(applied, node.callee.property).find(token => token.value === ".");
return fixer.replaceTextRange([propertyDot.range[0], node.range[1]], `(...${sourceCode.getText(node.arguments[1])})`);
}
});
} }
} }
}; };

130
tools/eslint/lib/rules/prefer-template.js

@ -36,6 +36,20 @@ function getTopConcatBinaryExpression(node) {
return node; return node;
} }
/**
* Checks whether or not a given binary expression has string literals.
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node has string literals.
*/
function hasStringLiteral(node) {
if (isConcatenation(node)) {
// `left` is deeper than `right` normally.
return hasStringLiteral(node.right) || hasStringLiteral(node.left);
}
return astUtils.isStringLiteral(node);
}
/** /**
* Checks whether or not a given binary expression has non string literals. * Checks whether or not a given binary expression has non string literals.
* @param {ASTNode} node - A node to check. * @param {ASTNode} node - A node to check.
@ -50,6 +64,36 @@ function hasNonStringLiteral(node) {
return !astUtils.isStringLiteral(node); return !astUtils.isStringLiteral(node);
} }
/**
* Determines whether a given node will start with a template curly expression (`${}`) when being converted to a template literal.
* @param {ASTNode} node The node that will be fixed to a template literal
* @returns {boolean} `true` if the node will start with a template curly.
*/
function startsWithTemplateCurly(node) {
if (node.type === "BinaryExpression") {
return startsWithTemplateCurly(node.left);
}
if (node.type === "TemplateLiteral") {
return node.expressions.length && node.quasis.length && node.quasis[0].start === node.quasis[0].end;
}
return node.type !== "Literal" || typeof node.value !== "string";
}
/**
* Determines whether a given node end with a template curly expression (`${}`) when being converted to a template literal.
* @param {ASTNode} node The node that will be fixed to a template literal
* @returns {boolean} `true` if the node will end with a template curly.
*/
function endsWithTemplateCurly(node) {
if (node.type === "BinaryExpression") {
return startsWithTemplateCurly(node.right);
}
if (node.type === "TemplateLiteral") {
return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].start === node.quasis[node.quasis.length - 1].end;
}
return node.type !== "Literal" || typeof node.value !== "string";
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -62,12 +106,86 @@ module.exports = {
recommended: false recommended: false
}, },
schema: [] schema: [],
fixable: "code"
}, },
create(context) { create(context) {
const sourceCode = context.getSourceCode();
let done = Object.create(null); let done = Object.create(null);
/**
* Gets the non-token text between two nodes, ignoring any other tokens that appear between the two tokens.
* @param {ASTNode} node1 The first node
* @param {ASTNode} node2 The second node
* @returns {string} The text between the nodes, excluding other tokens
*/
function getTextBetween(node1, node2) {
const allTokens = [node1].concat(sourceCode.getTokensBetween(node1, node2)).concat(node2);
const sourceText = sourceCode.getText();
return allTokens.slice(0, -1).reduce((accumulator, token, index) => accumulator + sourceText.slice(token.range[1], allTokens[index + 1].range[0]), "");
}
/**
* Returns a template literal form of the given node.
* @param {ASTNode} currentNode A node that should be converted to a template literal
* @param {string} textBeforeNode Text that should appear before the node
* @param {string} textAfterNode Text that should appear after the node
* @returns {string} A string form of this node, represented as a template literal
*/
function getTemplateLiteral(currentNode, textBeforeNode, textAfterNode) {
if (currentNode.type === "Literal" && typeof currentNode.value === "string") {
// If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted
// as a template placeholder. However, if the code already contains a backslash before the ${ or `
// for some reason, don't add another backslash, because that would change the meaning of the code (it would cause
// an actual backslash character to appear before the dollar sign).
return `\`${currentNode.raw.slice(1, -1).replace(/\\*(\${|`)/g, matched => {
if (matched.lastIndexOf("\\") % 2) {
return `\\${matched}`;
}
return matched;
// Unescape any quotes that appear in the original Literal that no longer need to be escaped.
}).replace(new RegExp(`\\\\${currentNode.raw[0]}`, "g"), currentNode.raw[0])}\``;
}
if (currentNode.type === "TemplateLiteral") {
return sourceCode.getText(currentNode);
}
if (isConcatenation(currentNode) && hasStringLiteral(currentNode) && hasNonStringLiteral(currentNode)) {
const plusSign = sourceCode.getTokensBetween(currentNode.left, currentNode.right).find(token => token.value === "+");
const textBeforePlus = getTextBetween(currentNode.left, plusSign);
const textAfterPlus = getTextBetween(plusSign, currentNode.right);
const leftEndsWithCurly = endsWithTemplateCurly(currentNode.left);
const rightStartsWithCurly = startsWithTemplateCurly(currentNode.right);
if (leftEndsWithCurly) {
// If the left side of the expression ends with a template curly, add the extra text to the end of the curly bracket.
// `foo${bar}` /* comment */ + 'baz' --> `foo${bar /* comment */ }${baz}`
return getTemplateLiteral(currentNode.left, textBeforeNode, textBeforePlus + textAfterPlus).slice(0, -1) +
getTemplateLiteral(currentNode.right, null, textAfterNode).slice(1);
}
if (rightStartsWithCurly) {
// Otherwise, if the right side of the expression starts with a template curly, add the text there.
// 'foo' /* comment */ + `${bar}baz` --> `foo${ /* comment */ bar}baz`
return getTemplateLiteral(currentNode.left, textBeforeNode, null).slice(0, -1) +
getTemplateLiteral(currentNode.right, textBeforePlus + textAfterPlus, textAfterNode).slice(1);
}
// Otherwise, these nodes should not be combined into a template curly, since there is nowhere to put
// the text between them.
return `${getTemplateLiteral(currentNode.left, textBeforeNode, null)}${textBeforePlus}+${textAfterPlus}${getTemplateLiteral(currentNode.right, textAfterNode, null)}`;
}
return `\`\${${textBeforeNode || ""}${sourceCode.getText(currentNode)}${textAfterNode || ""}}\``;
}
/** /**
* Reports if a given node is string concatenation with non string literals. * Reports if a given node is string concatenation with non string literals.
* *
@ -88,9 +206,13 @@ module.exports = {
done[topBinaryExpr.range[0]] = true; done[topBinaryExpr.range[0]] = true;
if (hasNonStringLiteral(topBinaryExpr)) { if (hasNonStringLiteral(topBinaryExpr)) {
context.report( context.report({
topBinaryExpr, node: topBinaryExpr,
"Unexpected string concatenation."); message: "Unexpected string concatenation.",
fix(fixer) {
return fixer.replaceText(topBinaryExpr, getTemplateLiteral(topBinaryExpr, null, null));
}
});
} }
} }

106
tools/eslint/lib/rules/quote-props.js

@ -61,7 +61,9 @@ module.exports = {
maxItems: 2 maxItems: 2
} }
] ]
} },
fixable: "code"
}, },
create(context) { create(context) {
@ -74,7 +76,8 @@ module.exports = {
MESSAGE_UNNECESSARY = "Unnecessarily quoted property '{{property}}' found.", MESSAGE_UNNECESSARY = "Unnecessarily quoted property '{{property}}' found.",
MESSAGE_UNQUOTED = "Unquoted property '{{property}}' found.", MESSAGE_UNQUOTED = "Unquoted property '{{property}}' found.",
MESSAGE_NUMERIC = "Unquoted number literal '{{property}}' used as key.", MESSAGE_NUMERIC = "Unquoted number literal '{{property}}' used as key.",
MESSAGE_RESERVED = "Unquoted reserved word '{{property}}' used as key."; MESSAGE_RESERVED = "Unquoted reserved word '{{property}}' used as key.",
sourceCode = context.getSourceCode();
/** /**
@ -100,6 +103,31 @@ module.exports = {
(tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value)); (tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value));
} }
/**
* Returns a string representation of a property node with quotes removed
* @param {ASTNode} key Key AST Node, which may or may not be quoted
* @returns {string} A replacement string for this property
*/
function getUnquotedKey(key) {
return key.type === "Identifier" ? key.name : key.value;
}
/**
* Returns a string representation of a property node with quotes added
* @param {ASTNode} key Key AST Node, which may or may not be quoted
* @returns {string} A replacement string for this property
*/
function getQuotedKey(key) {
if (key.type === "Literal" && typeof key.value === "string") {
// If the key is already a string literal, don't replace the quotes with double quotes.
return sourceCode.getText(key);
}
// Otherwise, the key is either an identifier or a number literal.
return `"${key.type === "Identifier" ? key.name : key.value}"`;
}
/** /**
* Ensures that a property's key is quoted only when necessary * Ensures that a property's key is quoted only when necessary
* @param {ASTNode} node Property AST node * @param {ASTNode} node Property AST node
@ -131,12 +159,27 @@ module.exports = {
} }
if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) { if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) {
context.report(node, MESSAGE_UNNECESSARY, {property: key.value}); context.report({
node,
message: MESSAGE_UNNECESSARY,
data: {property: key.value},
fix: fixer => fixer.replaceText(key, getUnquotedKey(key))
});
} }
} else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) { } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) {
context.report(node, MESSAGE_RESERVED, {property: key.name}); context.report({
node,
message: MESSAGE_RESERVED,
data: {property: key.name},
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
});
} else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") { } else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") {
context.report(node, MESSAGE_NUMERIC, {property: key.value}); context.report({
node,
message: MESSAGE_NUMERIC,
data: {property: key.value},
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
});
} }
} }
@ -149,8 +192,11 @@ module.exports = {
const key = node.key; const key = node.key;
if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) { if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) {
context.report(node, MESSAGE_UNQUOTED, { context.report({
property: key.name || key.value node,
message: MESSAGE_UNQUOTED,
data: {property: key.name || key.value},
fix: fixer => fixer.replaceText(key, getQuotedKey(key))
}); });
} }
} }
@ -162,8 +208,9 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function checkConsistency(node, checkQuotesRedundancy) { function checkConsistency(node, checkQuotesRedundancy) {
let quotes = false, const quotedProps = [],
lackOfQuotes = false, unquotedProps = [];
let keywordKeyName = null,
necessaryQuotes = false; necessaryQuotes = false;
node.properties.forEach(function(property) { node.properties.forEach(function(property) {
@ -176,7 +223,7 @@ module.exports = {
if (key.type === "Literal" && typeof key.value === "string") { if (key.type === "Literal" && typeof key.value === "string") {
quotes = true; quotedProps.push(property);
if (checkQuotesRedundancy) { if (checkQuotesRedundancy) {
try { try {
@ -189,21 +236,40 @@ module.exports = {
necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value); necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value);
} }
} else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) { } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) {
unquotedProps.push(property);
necessaryQuotes = true; necessaryQuotes = true;
context.report(node, "Properties should be quoted as '{{property}}' is a reserved word.", {property: key.name}); keywordKeyName = key.name;
} else { } else {
lackOfQuotes = true; unquotedProps.push(property);
}
if (quotes && lackOfQuotes) {
context.report(node, "Inconsistently quoted property '{{key}}' found.", {
key: key.name || key.value
});
} }
}); });
if (checkQuotesRedundancy && quotes && !necessaryQuotes) { if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) {
context.report(node, "Properties shouldn't be quoted as all quotes are redundant."); quotedProps.forEach(property => {
context.report({
node: property,
message: "Properties shouldn't be quoted as all quotes are redundant.",
fix: fixer => fixer.replaceText(property.key, getUnquotedKey(property.key))
});
});
} else if (unquotedProps.length && keywordKeyName) {
unquotedProps.forEach(property => {
context.report({
node: property,
message: "Properties should be quoted as '{{property}}' is a reserved word.",
data: {property: keywordKeyName},
fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
});
});
} else if (quotedProps.length && unquotedProps.length) {
unquotedProps.forEach(property => {
context.report({
node: property,
message: "Inconsistently quoted property '{{key}}' found.",
data: {key: property.key.name || property.key.value},
fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
});
});
} }
} }

24
tools/eslint/lib/rules/quotes.js

@ -123,12 +123,26 @@ module.exports = {
/** /**
* Determines if a given node is part of JSX syntax. * Determines if a given node is part of JSX syntax.
* @param {ASTNode} node The node to check. *
* @returns {boolean} True if the node is a JSX node, false if not. * This function returns `true` in the following cases:
*
* - `<div className="foo"></div>` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`.
* - `<div>foo</div>` ... If the literal is a text content, the parent of the literal is `JSXElement`.
*
* In particular, this function returns `false` in the following cases:
*
* - `<div className={"foo"}></div>`
* - `<div>{"foo"}</div>`
*
* In both cases, inside of the braces is handled as normal JavaScript.
* The braces are `JSXExpressionContainer` nodes.
*
* @param {ASTNode} node The Literal node to check.
* @returns {boolean} True if the node is a part of JSX, false if not.
* @private * @private
*/ */
function isJSXElement(node) { function isJSXLiteral(node) {
return node.type.indexOf("JSX") === 0; return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement";
} }
/** /**
@ -215,7 +229,7 @@ module.exports = {
if (settings && typeof val === "string") { if (settings && typeof val === "string") {
isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) || isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
isJSXElement(node.parent) || isJSXLiteral(node) ||
astUtils.isSurroundedBy(rawVal, settings.quote); astUtils.isSurroundedBy(rawVal, settings.quote);
if (!isValid && avoidEscape) { if (!isValid && avoidEscape) {

2
tools/eslint/lib/rules/semi.js

@ -53,7 +53,7 @@ module.exports = {
create(context) { create(context) {
const OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+- const OPT_OUT_PATTERN = /^[-[(\/+]$/; // One of [(/+-, but not ++ or --
const options = context.options[1]; const options = context.options[1];
const never = context.options[0] === "never", const never = context.options[0] === "never",
exceptOneLine = options && options.omitLastInOneLineBlock === true, exceptOneLine = options && options.omitLastInOneLineBlock === true,

4
tools/eslint/lib/rules/sort-keys.js

@ -1,5 +1,5 @@
/** /**
* @fileoverview Rule to requires object keys to be sorted * @fileoverview Rule to require object keys to be sorted
* @author Toru Nagashima * @author Toru Nagashima
*/ */
@ -74,7 +74,7 @@ const isValidOrders = {
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "requires object keys to be sorted", description: "require object keys to be sorted",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

40
tools/eslint/lib/rules/space-before-function-paren.js

@ -32,6 +32,9 @@ module.exports = {
}, },
named: { named: {
enum: ["always", "never", "ignore"] enum: ["always", "never", "ignore"]
},
asyncArrow: {
enum: ["always", "never", "ignore"]
} }
}, },
additionalProperties: false additionalProperties: false
@ -48,7 +51,9 @@ module.exports = {
let requireAnonymousFunctionSpacing = true, let requireAnonymousFunctionSpacing = true,
forbidAnonymousFunctionSpacing = false, forbidAnonymousFunctionSpacing = false,
requireNamedFunctionSpacing = true, requireNamedFunctionSpacing = true,
forbidNamedFunctionSpacing = false; forbidNamedFunctionSpacing = false,
requireArrowFunctionSpacing = false,
forbidArrowFunctionSpacing = false;
if (typeof configuration === "object") { if (typeof configuration === "object") {
requireAnonymousFunctionSpacing = ( requireAnonymousFunctionSpacing = (
@ -57,6 +62,8 @@ module.exports = {
requireNamedFunctionSpacing = ( requireNamedFunctionSpacing = (
!configuration.named || configuration.named === "always"); !configuration.named || configuration.named === "always");
forbidNamedFunctionSpacing = configuration.named === "never"; forbidNamedFunctionSpacing = configuration.named === "never";
requireArrowFunctionSpacing = configuration.asyncArrow === "always";
forbidArrowFunctionSpacing = configuration.asyncArrow === "never";
} else if (configuration === "never") { } else if (configuration === "never") {
requireAnonymousFunctionSpacing = false; requireAnonymousFunctionSpacing = false;
forbidAnonymousFunctionSpacing = true; forbidAnonymousFunctionSpacing = true;
@ -92,13 +99,31 @@ module.exports = {
* @returns {void} * @returns {void}
*/ */
function validateSpacingBeforeParentheses(node) { function validateSpacingBeforeParentheses(node) {
const isNamed = isNamedFunction(node); const isArrow = node.type === "ArrowFunctionExpression";
let rightToken; const isNamed = !isArrow && isNamedFunction(node);
const isAnonymousGenerator = node.generator && !isNamed;
const isNormalArrow = isArrow && !node.async;
const isArrowWithoutParens = isArrow && sourceCode.getFirstToken(node, 1).value !== "(";
let forbidSpacing, requireSpacing, rightToken;
if (node.generator && !isNamed) { // isAnonymousGenerator → `generator-star-spacing` should warn it. E.g. `function* () {}`
// isNormalArrow → ignore always.
// isArrowWithoutParens → ignore always. E.g. `async a => a`
if (isAnonymousGenerator || isNormalArrow || isArrowWithoutParens) {
return; return;
} }
if (isArrow) {
forbidSpacing = forbidArrowFunctionSpacing;
requireSpacing = requireArrowFunctionSpacing;
} else if (isNamed) {
forbidSpacing = forbidNamedFunctionSpacing;
requireSpacing = requireNamedFunctionSpacing;
} else {
forbidSpacing = forbidAnonymousFunctionSpacing;
requireSpacing = requireAnonymousFunctionSpacing;
}
rightToken = sourceCode.getFirstToken(node); rightToken = sourceCode.getFirstToken(node);
while (rightToken.value !== "(") { while (rightToken.value !== "(") {
rightToken = sourceCode.getTokenAfter(rightToken); rightToken = sourceCode.getTokenAfter(rightToken);
@ -107,7 +132,7 @@ module.exports = {
const location = leftToken.loc.end; const location = leftToken.loc.end;
if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) {
if ((isNamed && forbidNamedFunctionSpacing) || (!isNamed && forbidAnonymousFunctionSpacing)) { if (forbidSpacing) {
context.report({ context.report({
node, node,
loc: location, loc: location,
@ -118,7 +143,7 @@ module.exports = {
}); });
} }
} else { } else {
if ((isNamed && requireNamedFunctionSpacing) || (!isNamed && requireAnonymousFunctionSpacing)) { if (requireSpacing) {
context.report({ context.report({
node, node,
loc: location, loc: location,
@ -133,7 +158,8 @@ module.exports = {
return { return {
FunctionDeclaration: validateSpacingBeforeParentheses, FunctionDeclaration: validateSpacingBeforeParentheses,
FunctionExpression: validateSpacingBeforeParentheses FunctionExpression: validateSpacingBeforeParentheses,
ArrowFunctionExpression: validateSpacingBeforeParentheses,
}; };
} }
}; };

2
tools/eslint/lib/rules/space-infix-ops.js

@ -11,7 +11,7 @@
module.exports = { module.exports = {
meta: { meta: {
docs: { docs: {
description: "require spacing around operators", description: "require spacing around infix operators",
category: "Stylistic Issues", category: "Stylistic Issues",
recommended: false recommended: false
}, },

14
tools/eslint/lib/rules/space-unary-ops.js

@ -176,6 +176,17 @@ module.exports = {
checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
} }
/**
* Verifies AwaitExpressions satisfy spacing requirements
* @param {ASTNode} node AwaitExpression AST node
* @returns {void}
*/
function checkForSpacesAfterAwait(node) {
const tokens = sourceCode.getFirstTokens(node, 3);
checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await");
}
/** /**
* Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator
* @param {ASTnode} node AST node * @param {ASTnode} node AST node
@ -291,7 +302,8 @@ module.exports = {
UnaryExpression: checkForSpaces, UnaryExpression: checkForSpaces,
UpdateExpression: checkForSpaces, UpdateExpression: checkForSpaces,
NewExpression: checkForSpaces, NewExpression: checkForSpaces,
YieldExpression: checkForSpacesAfterYield YieldExpression: checkForSpacesAfterYield,
AwaitExpression: checkForSpacesAfterAwait
}; };
} }

57
tools/eslint/lib/rules/strict.js

@ -88,7 +88,9 @@ module.exports = {
{ {
enum: ["never", "global", "function", "safe"] enum: ["never", "global", "function", "safe"]
} }
] ],
fixable: "code"
}, },
create(context) { create(context) {
@ -104,40 +106,59 @@ module.exports = {
mode = ecmaFeatures.globalReturn ? "global" : "function"; mode = ecmaFeatures.globalReturn ? "global" : "function";
} }
/**
* Determines whether a reported error should be fixed, depending on the error type.
* @param {string} errorType The type of error
* @returns {boolean} `true` if the reported error should be fixed
*/
function shouldFix(errorType) {
return errorType === "multiple" || errorType === "unnecessary" || errorType === "module" || errorType === "implied" || errorType === "unnecessaryInClasses";
}
/**
* Gets a fixer function to remove a given 'use strict' directive.
* @param {ASTNode} node The directive that should be removed
* @returns {Function} A fixer function
*/
function getFixFunction(node) {
return fixer => fixer.remove(node);
}
/** /**
* Report a slice of an array of nodes with a given message. * Report a slice of an array of nodes with a given message.
* @param {ASTNode[]} nodes Nodes. * @param {ASTNode[]} nodes Nodes.
* @param {string} start Index to start from. * @param {string} start Index to start from.
* @param {string} end Index to end before. * @param {string} end Index to end before.
* @param {string} message Message to display. * @param {string} message Message to display.
* @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
* @returns {void} * @returns {void}
*/ */
function reportSlice(nodes, start, end, message) { function reportSlice(nodes, start, end, message, fix) {
let i; nodes.slice(start, end).forEach(node => {
context.report({node, message, fix: fix ? getFixFunction(node) : null});
for (i = start; i < end; i++) { });
context.report(nodes[i], message);
}
} }
/** /**
* Report all nodes in an array with a given message. * Report all nodes in an array with a given message.
* @param {ASTNode[]} nodes Nodes. * @param {ASTNode[]} nodes Nodes.
* @param {string} message Message to display. * @param {string} message Message to display.
* @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
* @returns {void} * @returns {void}
*/ */
function reportAll(nodes, message) { function reportAll(nodes, message, fix) {
reportSlice(nodes, 0, nodes.length, message); reportSlice(nodes, 0, nodes.length, message, fix);
} }
/** /**
* Report all nodes in an array, except the first, with a given message. * Report all nodes in an array, except the first, with a given message.
* @param {ASTNode[]} nodes Nodes. * @param {ASTNode[]} nodes Nodes.
* @param {string} message Message to display. * @param {string} message Message to display.
* @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
* @returns {void} * @returns {void}
*/ */
function reportAllExceptFirst(nodes, message) { function reportAllExceptFirst(nodes, message, fix) {
reportSlice(nodes, 1, nodes.length, message); reportSlice(nodes, 1, nodes.length, message, fix);
} }
/** /**
@ -157,12 +178,12 @@ module.exports = {
if (!isSimpleParameterList(node.params)) { if (!isSimpleParameterList(node.params)) {
context.report(useStrictDirectives[0], messages.nonSimpleParameterList); context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
} else if (isParentStrict) { } else if (isParentStrict) {
context.report(useStrictDirectives[0], messages.unnecessary); context.report({node: useStrictDirectives[0], message: messages.unnecessary, fix: getFixFunction(useStrictDirectives[0])});
} else if (isInClass) { } else if (isInClass) {
context.report(useStrictDirectives[0], messages.unnecessaryInClasses); context.report({node: useStrictDirectives[0], message: messages.unnecessaryInClasses, fix: getFixFunction(useStrictDirectives[0])});
} }
reportAllExceptFirst(useStrictDirectives, messages.multiple); reportAllExceptFirst(useStrictDirectives, messages.multiple, true);
} else if (isParentGlobal) { } else if (isParentGlobal) {
if (isSimpleParameterList(node.params)) { if (isSimpleParameterList(node.params)) {
context.report(node, messages.function); context.report(node, messages.function);
@ -198,10 +219,10 @@ module.exports = {
enterFunctionInFunctionMode(node, useStrictDirectives); enterFunctionInFunctionMode(node, useStrictDirectives);
} else if (useStrictDirectives.length > 0) { } else if (useStrictDirectives.length > 0) {
if (isSimpleParameterList(node.params)) { if (isSimpleParameterList(node.params)) {
reportAll(useStrictDirectives, messages[mode]); reportAll(useStrictDirectives, messages[mode], shouldFix(mode));
} else { } else {
context.report(useStrictDirectives[0], messages.nonSimpleParameterList); context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
reportAllExceptFirst(useStrictDirectives, messages.multiple); reportAllExceptFirst(useStrictDirectives, messages.multiple, true);
} }
} }
} }
@ -218,9 +239,9 @@ module.exports = {
if (node.body.length > 0 && useStrictDirectives.length === 0) { if (node.body.length > 0 && useStrictDirectives.length === 0) {
context.report(node, messages.global); context.report(node, messages.global);
} }
reportAllExceptFirst(useStrictDirectives, messages.multiple); reportAllExceptFirst(useStrictDirectives, messages.multiple, true);
} else { } else {
reportAll(useStrictDirectives, messages[mode]); reportAll(useStrictDirectives, messages[mode], shouldFix(mode));
} }
}, },
FunctionDeclaration: enterFunction, FunctionDeclaration: enterFunction,

6
tools/eslint/lib/rules/valid-jsdoc.js

@ -165,7 +165,7 @@ module.exports = {
} }
/** /**
* Check if return tag type is void or undefined * Validate type for a given JSDoc node
* @param {Object} jsdocNode JSDoc node * @param {Object} jsdocNode JSDoc node
* @param {Object} type JSDoc tag * @param {Object} type JSDoc tag
* @returns {void} * @returns {void}
@ -192,7 +192,9 @@ module.exports = {
elements = type.elements; elements = type.elements;
break; break;
case "FieldType": // Array.<{count: number, votes: number}> case "FieldType": // Array.<{count: number, votes: number}>
typesToCheck.push(getCurrentExpectedTypes(type.value)); if (type.value) {
typesToCheck.push(getCurrentExpectedTypes(type.value));
}
break; break;
default: default:
typesToCheck.push(getCurrentExpectedTypes(type)); typesToCheck.push(getCurrentExpectedTypes(type));

21
tools/eslint/lib/rules/valid-typeof.js

@ -34,6 +34,17 @@ module.exports = {
const VALID_TYPES = ["symbol", "undefined", "object", "boolean", "number", "string", "function"], const VALID_TYPES = ["symbol", "undefined", "object", "boolean", "number", "string", "function"],
OPERATORS = ["==", "===", "!=", "!=="]; OPERATORS = ["==", "===", "!=", "!=="];
const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals;
/**
* Determines whether a node is a typeof expression.
* @param {ASTNode} node The node
* @returns {boolean} `true` if the node is a typeof expression
*/
function isTypeofExpression(node) {
return node.type === "UnaryExpression" && node.operator === "typeof";
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@ -41,17 +52,19 @@ module.exports = {
return { return {
UnaryExpression(node) { UnaryExpression(node) {
if (node.operator === "typeof") { if (isTypeofExpression(node)) {
const parent = context.getAncestors().pop(); const parent = context.getAncestors().pop();
if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) { if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) {
const sibling = parent.left === node ? parent.right : parent.left; const sibling = parent.left === node ? parent.right : parent.left;
if (sibling.type === "Literal") { if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) {
if (VALID_TYPES.indexOf(sibling.value) === -1) { const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked;
if (VALID_TYPES.indexOf(value) === -1) {
context.report(sibling, "Invalid typeof comparison value."); context.report(sibling, "Invalid typeof comparison value.");
} }
} else if (context.options[0] && context.options[0].requireStringLiterals) { } else if (requireStringLiterals && !isTypeofExpression(sibling)) {
context.report(sibling, "Typeof comparisons should be to string literals."); context.report(sibling, "Typeof comparisons should be to string literals.");
} }
} }

111
tools/eslint/lib/rules/wrap-iife.js

@ -5,6 +5,8 @@
"use strict"; "use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Rule Definition // Rule Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -20,13 +22,25 @@ module.exports = {
schema: [ schema: [
{ {
enum: ["outside", "inside", "any"] enum: ["outside", "inside", "any"]
},
{
type: "object",
properties: {
functionPrototypeMethods: {
type: "boolean"
}
},
additionalProperties: false
} }
] ],
fixable: "code"
}, },
create(context) { create(context) {
const style = context.options[0] || "outside"; const style = context.options[0] || "outside";
const includeFunctionPrototypeMethods = (context.options[1] && context.options[1].functionPrototypeMethods) || false;
const sourceCode = context.getSourceCode(); const sourceCode = context.getSourceCode();
@ -44,20 +58,91 @@ module.exports = {
nextToken && nextToken.value === ")"; nextToken && nextToken.value === ")";
} }
return { /**
* Get the function node from an IIFE
* @param {ASTNode} node node to evaluate
* @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
*/
function getFunctionNodeFromIIFE(node) {
const callee = node.callee;
if (callee.type === "FunctionExpression") {
return callee;
}
if (includeFunctionPrototypeMethods &&
callee.type === "MemberExpression" &&
callee.object.type === "FunctionExpression" &&
(astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply")
) {
return callee.object;
}
return null;
}
return {
CallExpression(node) { CallExpression(node) {
if (node.callee.type === "FunctionExpression") { const innerNode = getFunctionNodeFromIIFE(node);
const callExpressionWrapped = wrapped(node),
functionExpressionWrapped = wrapped(node.callee); if (!innerNode) {
return;
if (!callExpressionWrapped && !functionExpressionWrapped) { }
context.report(node, "Wrap an immediate function invocation in parentheses.");
} else if (style === "inside" && !functionExpressionWrapped) { const callExpressionWrapped = wrapped(node),
context.report(node, "Wrap only the function expression in parens."); functionExpressionWrapped = wrapped(innerNode);
} else if (style === "outside" && !callExpressionWrapped) {
context.report(node, "Move the invocation into the parens that contain the function."); if (!callExpressionWrapped && !functionExpressionWrapped) {
} context.report({
node,
message: "Wrap an immediate function invocation in parentheses.",
fix(fixer) {
const nodeToSurround = style === "inside" ? innerNode : node;
return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
}
});
} else if (style === "inside" && !functionExpressionWrapped) {
context.report({
node,
message: "Wrap only the function expression in parens.",
fix(fixer) {
/*
* The outer call expression will always be wrapped at this point.
* Replace the range between the end of the function expression and the end of the call expression.
* for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
* Replace the parens from the outer expression, and parenthesize the function expression.
*/
const parenAfter = sourceCode.getTokenAfter(node);
return fixer.replaceTextRange(
[innerNode.range[1], parenAfter.range[1]],
`)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
);
}
});
} else if (style === "outside" && !callExpressionWrapped) {
context.report({
node,
message: "Move the invocation into the parens that contain the function.",
fix(fixer) {
/*
* The inner function expression will always be wrapped at this point.
* It's only necessary to replace the range between the end of the function expression
* and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
* should get replaced with `(bar))`.
*/
const parenAfter = sourceCode.getTokenAfter(innerNode);
return fixer.replaceTextRange(
[parenAfter.range[0], node.range[1]],
`${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`
);
}
});
} }
} }
}; };

67
tools/eslint/lib/rules/yoda.js

@ -141,7 +141,9 @@ module.exports = {
}, },
additionalProperties: false additionalProperties: false
} }
] ],
fixable: "code"
}, },
create(context) { create(context) {
@ -219,46 +221,57 @@ module.exports = {
isParenWrapped()); isParenWrapped());
} }
const OPERATOR_FLIP_MAP = {
"===": "===",
"!==": "!==",
"==": "==",
"!=": "!=",
"<": ">",
">": "<",
"<=": ">=",
">=": "<="
};
/**
* Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
* @param {ASTNode} node The BinaryExpression node
* @returns {string} A string representation of the node with the sides and operator flipped
*/
function getFlippedString(node) {
const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
const textBeforeOperator = sourceCode.getText().slice(sourceCode.getTokenBefore(operatorToken).range[1], operatorToken.range[0]);
const textAfterOperator = sourceCode.getText().slice(operatorToken.range[1], sourceCode.getTokenAfter(operatorToken).range[0]);
const leftText = sourceCode.getText().slice(sourceCode.getFirstToken(node).range[0], sourceCode.getTokenBefore(operatorToken).range[1]);
const rightText = sourceCode.getText().slice(sourceCode.getTokenAfter(operatorToken).range[0], sourceCode.getLastToken(node).range[1]);
return rightText + textBeforeOperator + OPERATOR_FLIP_MAP[operatorToken.value] + textAfterOperator + leftText;
}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Public // Public
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
return { return {
BinaryExpression: always ? function(node) { BinaryExpression(node) {
const expectedLiteral = always ? node.left : node.right;
// Comparisons must always be yoda-style: if ("blue" === color) const expectedNonLiteral = always ? node.right : node.left;
if (
(node.right.type === "Literal" || looksLikeLiteral(node.right)) &&
!(node.left.type === "Literal" || looksLikeLiteral(node.left)) &&
!(!isEqualityOperator(node.operator) && onlyEquality) &&
isComparisonOperator(node.operator) &&
!(exceptRange && isRangeTest(context.getAncestors().pop()))
) {
context.report({
node,
message: "Expected literal to be on the left side of {{operator}}.",
data: {
operator: node.operator
}
});
}
} : function(node) {
// Comparisons must never be yoda-style (default) // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
if ( if (
(node.left.type === "Literal" || looksLikeLiteral(node.left)) && (expectedNonLiteral.type === "Literal" || looksLikeLiteral(expectedNonLiteral)) &&
!(node.right.type === "Literal" || looksLikeLiteral(node.right)) && !(expectedLiteral.type === "Literal" || looksLikeLiteral(expectedLiteral)) &&
!(!isEqualityOperator(node.operator) && onlyEquality) && !(!isEqualityOperator(node.operator) && onlyEquality) &&
isComparisonOperator(node.operator) && isComparisonOperator(node.operator) &&
!(exceptRange && isRangeTest(context.getAncestors().pop())) !(exceptRange && isRangeTest(context.getAncestors().pop()))
) { ) {
context.report({ context.report({
node, node,
message: "Expected literal to be on the right side of {{operator}}.", message: "Expected literal to be on the {{expectedSide}} side of {{operator}}.",
data: { data: {
operator: node.operator operator: node.operator,
} expectedSide: always ? "left" : "right"
},
fix: fixer => fixer.replaceText(node, getFlippedString(node))
}); });
} }

68
tools/eslint/lib/testers/rule-tester.js

@ -182,13 +182,53 @@ RuleTester.resetDefaultConfig = function() {
}; };
// default separators for testing // default separators for testing
RuleTester.describe = (typeof describe === "function") ? describe : /* istanbul ignore next */ function(text, method) { const DESCRIBE = Symbol("describe");
return method.apply(this); const IT = Symbol("it");
};
RuleTester[DESCRIBE] = RuleTester[IT] = null;
RuleTester.it = (typeof it === "function") ? it : /* istanbul ignore next */ function(text, method) { /**
* This is `it` or `describe` if those don't exist.
* @this {Mocha}
* @param {string} text - The description of the test case.
* @param {Function} method - The logic of the test case.
* @returns {any} Returned value of `method`.
*/
function defaultHandler(text, method) {
return method.apply(this); return method.apply(this);
}; }
// If people use `mocha test.js --watch` command, `describe` and `it` function
// instances are different for each execution. So this should get fresh instance
// always.
Object.defineProperties(RuleTester, {
describe: {
get() {
return (
RuleTester[DESCRIBE] ||
(typeof describe === "function" ? describe : defaultHandler)
);
},
set(value) {
RuleTester[DESCRIBE] = value;
},
configurable: true,
enumerable: true,
},
it: {
get() {
return (
RuleTester[IT] ||
(typeof it === "function" ? it : defaultHandler)
);
},
set(value) {
RuleTester[IT] = value;
},
configurable: true,
enumerable: true,
},
});
RuleTester.prototype = { RuleTester.prototype = {
@ -266,9 +306,9 @@ RuleTester.prototype = {
if (validateSchema.errors) { if (validateSchema.errors) {
throw new Error([ throw new Error([
"Schema for rule " + ruleName + " is invalid:" `Schema for rule ${ruleName} is invalid:`
].concat(validateSchema.errors.map(function(error) { ].concat(validateSchema.errors.map(function(error) {
return "\t" + error.field + ": " + error.message; return `\t${error.field}: ${error.message}`;
})).join("\n")); })).join("\n"));
} }
} }
@ -373,7 +413,7 @@ RuleTester.prototype = {
*/ */
function testInvalidTemplate(ruleName, item) { function testInvalidTemplate(ruleName, item) {
assert.ok(item.errors || item.errors === 0, assert.ok(item.errors || item.errors === 0,
"Did not specify errors for an invalid test of " + ruleName); `Did not specify errors for an invalid test of ${ruleName}`);
const result = runRuleForItem(ruleName, item); const result = runRuleForItem(ruleName, item);
const messages = result.messages; const messages = result.messages;
@ -389,7 +429,7 @@ RuleTester.prototype = {
item.errors.length, item.errors.length === 1 ? "" : "s", messages.length, util.inspect(messages))); item.errors.length, item.errors.length === 1 ? "" : "s", messages.length, util.inspect(messages)));
for (let i = 0, l = item.errors.length; i < l; i++) { for (let i = 0, l = item.errors.length; i < l; i++) {
assert.ok(!("fatal" in messages[i]), "A fatal parsing error occurred: " + messages[i].message); assert.ok(!("fatal" in messages[i]), `A fatal parsing error occurred: ${messages[i].message}`);
assert.equal(messages[i].ruleId, ruleName, "Error rule name should be the same as the name of the rule being tested"); assert.equal(messages[i].ruleId, ruleName, "Error rule name should be the same as the name of the rule being tested");
if (typeof item.errors[i] === "string") { if (typeof item.errors[i] === "string") {
@ -408,23 +448,23 @@ RuleTester.prototype = {
} }
if (item.errors[i].type) { if (item.errors[i].type) {
assert.equal(messages[i].nodeType, item.errors[i].type, "Error type should be " + item.errors[i].type); assert.equal(messages[i].nodeType, item.errors[i].type, `Error type should be ${item.errors[i].type}`);
} }
if (item.errors[i].hasOwnProperty("line")) { if (item.errors[i].hasOwnProperty("line")) {
assert.equal(messages[i].line, item.errors[i].line, "Error line should be " + item.errors[i].line); assert.equal(messages[i].line, item.errors[i].line, `Error line should be ${item.errors[i].line}`);
} }
if (item.errors[i].hasOwnProperty("column")) { if (item.errors[i].hasOwnProperty("column")) {
assert.equal(messages[i].column, item.errors[i].column, "Error column should be " + item.errors[i].column); assert.equal(messages[i].column, item.errors[i].column, `Error column should be ${item.errors[i].column}`);
} }
if (item.errors[i].hasOwnProperty("endLine")) { if (item.errors[i].hasOwnProperty("endLine")) {
assert.equal(messages[i].endLine, item.errors[i].endLine, "Error endLine should be " + item.errors[i].endLine); assert.equal(messages[i].endLine, item.errors[i].endLine, `Error endLine should be ${item.errors[i].endLine}`);
} }
if (item.errors[i].hasOwnProperty("endColumn")) { if (item.errors[i].hasOwnProperty("endColumn")) {
assert.equal(messages[i].endColumn, item.errors[i].endColumn, "Error endColumn should be " + item.errors[i].endColumn); assert.equal(messages[i].endColumn, item.errors[i].endColumn, `Error endColumn should be ${item.errors[i].endColumn}`);
} }
} else { } else {

2
tools/eslint/lib/timing.js

@ -66,7 +66,7 @@ function display(data) {
.slice(0, 10); .slice(0, 10);
rows.forEach(function(row) { rows.forEach(function(row) {
row.push((row[1] * 100 / total).toFixed(1) + "%"); row.push(`${(row[1] * 100 / total).toFixed(1)}%`);
row[1] = row[1].toFixed(3); row[1] = row[1].toFixed(3);
}); });

4
tools/eslint/lib/util/glob-util.js

@ -50,9 +50,9 @@ function processPath(options) {
let suffix = "/**"; let suffix = "/**";
if (extensions.length === 1) { if (extensions.length === 1) {
suffix += "/*." + extensions[0]; suffix += `/*.${extensions[0]}`;
} else { } else {
suffix += "/*.{" + extensions.join(",") + "}"; suffix += `/*.{${extensions.join(",")}}`;
} }
/** /**

2
tools/eslint/lib/util/module-resolver.js

@ -71,7 +71,7 @@ ModuleResolver.prototype = {
const result = Module._findPath(name, lookupPaths); // eslint-disable-line no-underscore-dangle const result = Module._findPath(name, lookupPaths); // eslint-disable-line no-underscore-dangle
if (!result) { if (!result) {
throw new Error("Cannot find module '" + name + "'"); throw new Error(`Cannot find module '${name}'`);
} }
return result; return result;

2
tools/eslint/lib/util/node-event-generator.js

@ -46,7 +46,7 @@ NodeEventGenerator.prototype = {
* @returns {void} * @returns {void}
*/ */
leaveNode: function leaveNode(node) { leaveNode: function leaveNode(node) {
this.emitter.emit(node.type + ":exit", node); this.emitter.emit(`${node.type}:exit`, node);
} }
}; };

2
tools/eslint/lib/util/npm-util.js

@ -53,7 +53,7 @@ function installSyncSaveDev(packages) {
if (Array.isArray(packages)) { if (Array.isArray(packages)) {
packages = packages.join(" "); packages = packages.join(" ");
} }
shell.exec("npm i --save-dev " + packages, {stdio: "inherit"}); shell.exec(`npm i --save-dev ${packages}`, {stdio: "inherit"});
} }
/** /**

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save