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. 22
      tools/eslint/bin/eslint.js
  4. 1
      tools/eslint/conf/eslint.json
  5. 23
      tools/eslint/lib/ast-utils.js
  6. 45
      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. 242
      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. 204
      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. 9
      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. 172
      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. 109
      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. 104
      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. 4
      tools/eslint/lib/rules/valid-jsdoc.js
  92. 21
      tools/eslint/lib/rules/valid-typeof.js
  93. 99
      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))
* Vitor Balocco ([@vitorbal](https://github.com/vitorbal))
* James Henry ([@JamesHenry](https://github.com/JamesHenry))
* Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
## Releases

22
tools/eslint/bin/eslint.js

@ -5,13 +5,15 @@
* @author Nicholas C. Zakas
*/
/* eslint no-console:off, no-process-exit:off */
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
var useStdIn = (process.argv.indexOf("--stdin") > -1),
const useStdIn = (process.argv.indexOf("--stdin") > -1),
init = (process.argv.indexOf("--init") > -1),
debug = (process.argv.indexOf("--debug") > -1);
@ -25,7 +27,7 @@ if (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"),
path = require("path"),
fs = require("fs");
@ -35,14 +37,15 @@ var concat = require("concat-stream"),
//------------------------------------------------------------------------------
process.on("uncaughtException", function(err) {
// lazy load
var lodash = require("lodash");
const lodash = require("lodash");
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("\n" + template(err.messageData || {}));
console.log(`\n${template(err.messageData || {})}`);
} else {
console.log(err.message);
console.log(err.stack);
@ -53,16 +56,11 @@ process.on("uncaughtException", function(err){
if (useStdIn) {
process.stdin.pipe(concat({ encoding: "string" }, function(text) {
try {
process.exitCode = cli.execute(process.argv, text);
} catch (ex) {
console.error(ex.message);
console.error(ex.stack);
process.exitCode = 1;
}
}));
} else if (init) {
var configInit = require("../lib/config/config-initializer");
const configInit = require("../lib/config/config-initializer");
configInit.initializeConfig(function(err) {
if (err) {
process.exitCode = 1;

1
tools/eslint/conf/eslint.json

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

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

@ -560,6 +560,7 @@ module.exports = {
/* falls through */
case "UnaryExpression":
case "AwaitExpression":
return 14;
case "UpdateExpression":
@ -715,5 +716,27 @@ module.exports = {
}
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);
}
};

45
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 {number} errorCount Number or errors 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 {
passNumber++;
debug("Linting code for " + options.filename + " (pass " + passNumber + ")");
debug(`Linting code for ${options.filename} (pass ${passNumber})`);
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);
// 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.
*/
if (fixedResult.fixed) {
@ -198,7 +200,7 @@ function multipassFix(text, config, options) {
* @param {string} filename An optional string representing the texts filename.
* @param {boolean} fix Indicates if fixes should be processed.
* @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
*/
function processText(text, configHelper, filename, fix, allowInlineConfig) {
@ -218,7 +220,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
}
filename = filename || "<text>";
debug("Linting " + filename);
debug(`Linting ${filename}`);
const config = configHelper.getConfig(filePath);
if (config.plugins) {
@ -279,6 +281,10 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
result.output = fixedResult.output;
}
if (result.errorCount + result.warningCount > 0 && typeof result.output === "undefined") {
result.source = text;
}
return result;
}
@ -288,7 +294,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
* @param {string} filename The filename of the file being checked.
* @param {Object} configHelper The configuration options for ESLint.
* @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
*/
function processFile(filename, configHelper, options) {
@ -304,7 +310,7 @@ function processFile(filename, configHelper, options) {
* Returns result with warning by ignore settings
* @param {string} filePath - File path of checked code
* @param {string} baseDir - Absolute path of base directory
* @returns {Result} Result with single warning
* @returns {LintResult} Result with single warning
* @private
*/
function createIgnoreResult(filePath, baseDir) {
@ -376,7 +382,7 @@ function getCacheFile(cacheFile, cwd) {
* @returns {string} the resolved path to the cacheFile
*/
function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, ".cache_" + hash(cwd));
return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
}
let fileStats;
@ -461,7 +467,7 @@ function CLIEngine(options) {
const cwd = this.options.cwd;
this.options.rulePaths.forEach(function(rulesdir) {
debug("Loading rules from " + rulesdir);
debug(`Loading rules from ${rulesdir}`);
rules.load(rulesdir, cwd);
});
}
@ -497,13 +503,13 @@ CLIEngine.getFormatter = function(format) {
formatterPath = path.resolve(cwd, format);
} else {
formatterPath = "./formatters/" + format;
formatterPath = `./formatters/${format}`;
}
try {
return require(formatterPath);
} 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;
}
@ -524,12 +530,13 @@ CLIEngine.getErrorResults = function(results) {
const filteredMessages = result.messages.filter(isErrorMessage);
if (filteredMessages.length > 0) {
filtered.push({
filePath: result.filePath,
filtered.push(
Object.assign(result, {
messages: filteredMessages,
errorCount: filteredMessages.length,
warningCount: 0
});
})
);
}
});
@ -608,7 +615,7 @@ CLIEngine.prototype = {
const eslintVersion = pkg.version;
prevConfig.hash = hash(eslintVersion + "_" + stringify(config));
prevConfig.hash = hash(`${eslintVersion}_${stringify(config)}`);
}
return prevConfig.hash;
@ -645,7 +652,7 @@ CLIEngine.prototype = {
const changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
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
@ -662,7 +669,7 @@ CLIEngine.prototype = {
fileCache.destroy();
}
debug("Processing " + filename);
debug(`Processing ${filename}`);
const res = processFile(filename, configHelper, options);
@ -674,7 +681,7 @@ CLIEngine.prototype = {
* next execution will also operate on this file
*/
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
fileCache.removeEntry(filename);
@ -713,7 +720,7 @@ CLIEngine.prototype = {
fileCache.reconcile();
}
debug("Linting complete in: " + (Date.now() - startTime) + "ms");
debug(`Linting complete in: ${Date.now() - startTime}ms`);
return {
results,

37
tools/eslint/lib/cli.js

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

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

@ -967,7 +967,7 @@ CodePathState.prototype = {
/* istanbul ignore next */
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(
this,
"internal",
{value: new CodePathState(new IdGenerator(id + "_"), onLooped)});
{value: new CodePathState(new IdGenerator(`${id}_`), onLooped)});
// Adds this into `childCodePaths` of `upper`.
if (upper) {

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

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

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

@ -187,7 +187,7 @@ ForkContext.prototype = {
* @returns {void}
*/
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));
},
@ -200,7 +200,7 @@ ForkContext.prototype = {
* @returns {void}
*/
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));
},

10
tools/eslint/lib/config.js

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

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

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

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

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

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

@ -44,8 +44,8 @@ function writeFile(config, format) {
extname = ".json";
}
ConfigFile.write(config, "./.eslintrc" + extname);
log.info("Successfully created .eslintrc" + extname + " file in " + process.cwd());
ConfigFile.write(config, `./.eslintrc${extname}`);
log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`);
if (config.installedESLint) {
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
if (config.plugins) {
modules = modules.concat(config.plugins.map(function(name) {
return "eslint-plugin-" + name;
return `eslint-plugin-${name}`;
}));
}
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
@ -93,7 +93,7 @@ function installModules(config) {
});
if (modulesToInstall.length > 0) {
log.info("Installing " + modulesToInstall.join(", "));
log.info(`Installing ${modulesToInstall.join(", ")}`);
npmUtil.installSyncSaveDev(modulesToInstall);
}
}
@ -150,7 +150,7 @@ function configureRules(answers, config) {
registry = registry.lintSourceCode(sourceCodes, newConfig, function(total) {
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
const recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
@ -198,9 +198,9 @@ function configureRules(answers, config) {
return (newConfig.rules[ruleId] !== 0);
}).length;
const resultMessage = [
"\nEnabled " + enabledRules + " out of " + totalRules,
"rules based on " + fileQty,
"file" + ((fileQty === 1) ? "." : "s.")
`\nEnabled ${enabledRules} out of ${totalRules}`,
`rules based on ${fileQty}`,
`file${(fileQty === 1) ? "." : "s."}`
].join(" ");
log.info(resultMessage);

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

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

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

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

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

@ -115,7 +115,7 @@ module.exports = {
let plugin = null;
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.messageData = {
@ -128,8 +128,8 @@ module.exports = {
try {
plugin = require(longName);
} catch (err) {
debug("Failed to load plugin " + longName + ".");
err.message = "Failed to load plugin " + pluginName + ": " + err.message;
debug(`Failed to load plugin ${longName}.`);
err.message = `Failed to load plugin ${pluginName}: ${err.message}`;
err.messageTemplate = "plugin-missing";
err.messageData = {
pluginName: longName

38
tools/eslint/lib/eslint.js

@ -97,7 +97,7 @@ function parseJsonConfig(string, location, messages) {
items = {};
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
try {
items = JSON.parse("{" + string + "}");
items = JSON.parse(`{${string}}`);
} catch (ex) {
messages.push({
@ -105,7 +105,7 @@ function parseJsonConfig(string, location, messages) {
fatal: true,
severity: 2,
source: null,
message: "Failed to parse JSON from '" + string + "': " + ex.message,
message: `Failed to parse JSON from '${string}': ${ex.message}`,
line: location.start.line,
column: location.start.column + 1
});
@ -350,7 +350,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
Object.keys(items).forEach(function(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;
});
break;
@ -446,7 +446,7 @@ function prepareConfig(config) {
const rule = config.rules[k];
if (rule === null) {
throw new Error("Invalid config for rule '" + k + "'\.");
throw new Error(`Invalid config for rule '${k}'\.`);
}
if (Array.isArray(rule)) {
copiedRules[k] = rule.slice();
@ -527,7 +527,7 @@ function getRuleReplacementMessage(ruleId) {
if (ruleId in replacements.rules) {
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;
@ -585,7 +585,6 @@ module.exports = (function() {
let messages = [],
currentConfig = null,
currentScopes = null,
scopeMap = null,
scopeManager = null,
currentFilename = null,
traverser = null,
@ -655,7 +654,7 @@ module.exports = (function() {
fatal: true,
severity: 2,
source,
message: "Parsing error: " + message,
message: `Parsing error: ${message}`,
line: ex.lineNumber,
column: ex.column
@ -706,7 +705,6 @@ module.exports = (function() {
messages = [];
currentConfig = null;
currentScopes = null;
scopeMap = null;
scopeManager = null;
traverser = null;
reportingConfig = [];
@ -783,7 +781,7 @@ module.exports = (function() {
ast = parse(
stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, function(match, captured) {
shebang = captured;
return "//" + captured;
return `//${captured}`;
}),
config,
currentFilename
@ -823,7 +821,7 @@ module.exports = (function() {
if (replacementMsg) {
ruleCreator = createStubRule(replacementMsg);
} else {
ruleCreator = createStubRule("Definition for rule '" + key + "' was not found");
ruleCreator = createStubRule(`Definition for rule '${key}' was not found`);
}
rules.define(key, ruleCreator);
}
@ -847,7 +845,7 @@ module.exports = (function() {
);
});
} catch (ex) {
ex.message = "Error while loading rule '" + key + "': " + ex.message;
ex.message = `Error while loading rule '${key}': ${ex.message}`;
throw ex;
}
});
@ -871,24 +869,6 @@ module.exports = (function() {
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
addDeclaredGlobals(ast, currentScopes[0], currentConfig);

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

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

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

@ -40,12 +40,12 @@ module.exports = function(results) {
messages.forEach(function(message) {
output += result.filePath + ": ";
output += "line " + (message.line || 0);
output += ", col " + (message.column || 0);
output += ", " + getMessageType(message);
output += " - " + message.message;
output += message.ruleId ? " (" + message.ruleId + ")" : "";
output += `${result.filePath}: `;
output += `line ${message.line || 0}`;
output += `, col ${message.column || 0}`;
output += `, ${getMessageType(message)}`;
output += ` - ${message.message}`;
output += message.ruleId ? ` (${message.ruleId})` : "";
output += "\n";
});
@ -53,7 +53,7 @@ module.exports = function(results) {
});
if (total > 0) {
output += "\n" + total + " problem" + (total !== 1 ? "s" : "");
output += `\n${total} problem${total !== 1 ? "s" : ""}`;
}
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.
*/
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) {
const totalProblems = totalErrors + totalWarnings;
let renderedText = totalProblems + " " + pluralize("problem", totalProblems);
let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`;
if (totalProblems !== 0) {
renderedText += " (" + totalErrors + " " + pluralize("error", totalErrors) + ", " + totalWarnings + " " + pluralize("warning", totalWarnings) + ")";
renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`;
}
return renderedText;
}

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

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

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

@ -40,21 +40,21 @@ module.exports = function(results) {
const messages = result.messages;
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) {
const type = message.fatal ? "error" : "failure";
output += "<testcase time=\"0\" name=\"org.eslint." + (message.ruleId || "unknown") + "\">";
output += "<" + type + " message=\"" + xmlEscape(message.message || "") + "\">";
output += `<testcase time="0" name="org.eslint.${message.ruleId || "unknown"}">`;
output += `<${type} message="${xmlEscape(message.message || "")}">`;
output += "<![CDATA[";
output += "line " + (message.line || 0) + ", col ";
output += (message.column || 0) + ", " + getMessageType(message);
output += " - " + xmlEscape(message.message || "");
output += (message.ruleId ? " (" + message.ruleId + ")" : "");
output += `line ${message.line || 0}, col `;
output += `${message.column || 0}, ${getMessageType(message)}`;
output += ` - ${xmlEscape(message.message || "")}`;
output += (message.ruleId ? ` (${message.ruleId})` : "");
output += "]]>";
output += "</" + type + ">";
output += `</${type}>`;
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.
*/
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;
output += chalk.underline(result.filePath) + "\n";
output += `${chalk.underline(result.filePath)}\n`;
output += table(
output += `${table(
messages.map(function(message) {
let messageType;
@ -73,9 +73,9 @@ module.exports = function(results) {
}
).split("\n").map(function(el) {
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) {

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

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

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

@ -30,9 +30,9 @@ function getMessageType(message) {
*/
function outputDiagnostics(diagnostic) {
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";
return output;
}
@ -42,7 +42,7 @@ function outputDiagnostics(diagnostic) {
//------------------------------------------------------------------------------
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) {
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 (messages.length > 0) {

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

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

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

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

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

@ -136,16 +136,16 @@ function IgnoredPaths(options) {
fs.statSync(options.ignorePath);
ignorePath = options.ignorePath;
} 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;
}
} else {
debug("Looking for ignore file in " + options.cwd);
debug(`Looking for ignore file in ${options.cwd}`);
ignorePath = findIgnoreFile(options.cwd);
try {
fs.statSync(ignorePath);
debug("Loaded ignore file " + ignorePath);
debug(`Loaded ignore file ${ignorePath}`);
} catch (e) {
debug("Could not find ignore file in cwd");
this.options = options;
@ -153,7 +153,7 @@ function IgnoredPaths(options) {
}
if (ignorePath) {
debug("Adding " + ignorePath);
debug(`Adding ${ignorePath}`);
this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
addIgnoreFile(this.ig.custom, 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",
type: "Boolean",
description: "Print the configuration to be used"
type: "path::String",
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) {
if (plugin.rules) {
Object.keys(plugin.rules).forEach(function(ruleId) {
const qualifiedRuleId = pluginName + "/" + ruleId,
const qualifiedRuleId = `${pluginName}/${ruleId}`,
rule = plugin.rules[ruleId];
define(qualifiedRuleId, rule);

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

@ -1,2 +1,3 @@
rules:
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:
TARGET_NODE_TYPE.test(node.type) &&
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}
*/
function parens(node) {
const token = sourceCode.getFirstToken(node);
const token = sourceCode.getFirstToken(node, node.async ? 1 : 0);
// "as-needed", { "requireForBlockBody": true }: x => x
if (

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

@ -16,9 +16,23 @@ module.exports = {
category: "Best Practices",
recommended: false
},
schema: []
schema: [{
type: "object",
properties: {
exceptMethods: {
type: "array",
items: {
type: "string"
}
}
},
additionalProperties: false
}]
},
create(context) {
const config = context.options[0] ? Object.assign({}, context.options[0]) : {};
const exceptMethods = new Set(config.exceptMethods || []);
const stack = [];
/**
@ -41,6 +55,16 @@ module.exports = {
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.
* Static methods and the constructor are exempt.
@ -52,7 +76,7 @@ module.exports = {
function exitFunction(node) {
const methodUsesThis = stack.pop();
if (isInstanceMethod(node.parent) && !methodUsesThis) {
if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
context.report({
node,
message: "Expected 'this' to be used by class method '{{classMethod}}'.",

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

@ -11,16 +11,62 @@
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.
* `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.
* @returns {boolean} `true` if a trailing comma is allowed.
*/
function isTrailingCommaAllowed(node, lastItem) {
return node.type !== "ArrayPattern" || lastItem.type !== "RestElement";
function isTrailingCommaAllowed(lastItem) {
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: [
{
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) {
const mode = context.options[0];
const options = normalizeOptions(context.options[0]);
const sourceCode = context.getSourceCode();
const UNEXPECTED_MESSAGE = "Unexpected 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.
* 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.
*/
function isMultiline(node) {
const lastItem = lodash.last(node.properties || node.elements || node.specifiers);
const lastItem = getLastItem(node);
if (!lastItem) {
return false;
}
const sourceCode = context.getSourceCode();
let penultimateToken = sourceCode.getLastToken(lastItem),
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);
}
const penultimateToken = getTrailingToken(node, lastItem);
const lastToken = sourceCode.getTokenAfter(penultimateToken);
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
}
@ -91,21 +221,13 @@ module.exports = {
* @returns {void}
*/
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")) {
return;
}
const sourceCode = context.getSourceCode();
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);
}
const trailingToken = getTrailingToken(node, lastItem);
if (trailingToken.value === ",") {
context.report({
@ -132,33 +254,25 @@ module.exports = {
* @returns {void}
*/
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")) {
return;
}
if (!isTrailingCommaAllowed(node, lastItem)) {
if (!isTrailingCommaAllowed(lastItem)) {
forbidTrailingComma(node);
return;
}
const sourceCode = context.getSourceCode();
let penultimateToken = lastItem,
trailingToken = sourceCode.getTokenAfter(lastItem);
// Skip close parentheses.
while (trailingToken.value === ")") {
penultimateToken = trailingToken;
trailingToken = sourceCode.getTokenAfter(trailingToken);
}
const trailingToken = getTrailingToken(node, lastItem);
if (trailingToken.value !== ",") {
context.report({
node: lastItem,
loc: lastItem.loc.end,
loc: trailingToken.loc.end,
message: MISSING_MESSAGE,
fix(fixer) {
return fixer.insertTextAfter(penultimateToken, ",");
return fixer.insertTextAfter(trailingToken, ",");
}
});
}
@ -198,26 +312,30 @@ module.exports = {
}
}
// Chooses a checking function.
let checkForTrailingComma;
if (mode === "always") {
checkForTrailingComma = forceTrailingComma;
} else if (mode === "always-multiline") {
checkForTrailingComma = forceTrailingCommaIfMultiline;
} else if (mode === "only-multiline") {
checkForTrailingComma = allowTrailingCommaIfMultiline;
} else {
checkForTrailingComma = forbidTrailingComma;
}
const predicate = {
always: forceTrailingComma,
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma,
ignore: lodash.noop,
};
return {
ObjectExpression: checkForTrailingComma,
ObjectPattern: checkForTrailingComma,
ArrayExpression: checkForTrailingComma,
ArrayPattern: checkForTrailingComma,
ImportDeclaration: checkForTrailingComma,
ExportNamedDeclaration: checkForTrailingComma
ObjectExpression: predicate[options.objects],
ObjectPattern: predicate[options.objects],
ArrayExpression: predicate[options.arrays],
ArrayPattern: predicate[options.arrays],
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 esUtils = require("esutils");
//------------------------------------------------------------------------------
// Rule Definition
@ -48,7 +49,9 @@ module.exports = {
maxItems: 2
}
]
}
},
fixable: "code"
},
create(context) {
@ -137,12 +140,13 @@ module.exports = {
/**
* Reports "Expected { after ..." error
* @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} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportExpectedBraceError(node, name, suffix) {
function reportExpectedBraceError(node, bodyNode, name, suffix) {
context.report({
node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
@ -150,19 +154,73 @@ module.exports = {
data: {
name,
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
* @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} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportUnnecessaryBraceError(node, name, suffix) {
function reportUnnecessaryBraceError(node, bodyNode, name, suffix) {
context.report({
node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
@ -170,6 +228,33 @@ module.exports = {
data: {
name,
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() {
if (this.expected !== null && this.expected !== this.actual) {
if (this.expected) {
reportExpectedBraceError(node, name, suffix);
reportExpectedBraceError(node, body, name, suffix);
} 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"]
}
]
],
fixable: "code"
},
create(context) {
@ -44,14 +46,28 @@ module.exports = {
*/
function checkDotLocation(obj, prop, node) {
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 (onObject) {
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)) {
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>
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -11,20 +17,17 @@
module.exports = {
meta: {
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",
recommended: false
},
fixable: "whitespace",
schema: [
{
enum: ["unix", "windows"]
enum: ["always", "never", "unix", "windows"]
}
]
},
create(context) {
//--------------------------------------------------------------------------
@ -32,31 +35,60 @@ module.exports = {
//--------------------------------------------------------------------------
return {
Program: function checkBadEOF(node) {
const sourceCode = context.getSourceCode(),
src = sourceCode.getText(),
location = {column: 1},
linebreakStyle = context.options[0] || "unix",
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";
location = {
column: lodash.last(sourceCode.lines).length,
line: sourceCode.lines.length
},
LF = "\n",
CRLF = `\r${LF}`,
endsWithNewline = lodash.endsWith(src, LF);
if (src[src.length - 1] !== "\n") {
let mode = context.options[0] || "always",
appendCRLF = false;
if (mode === "unix") {
// `"unix"` should behave exactly as `"always"`
mode = "always";
}
if (mode === "windows") {
// file is not newline-terminated
location.line = src.split(/\n/g).length;
// `"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, but should be
context.report({
node,
loc: location,
message: "Newline required at end of file but not found.",
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 = {
meta: {
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",
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 && (
// 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.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent)
);

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

@ -121,8 +121,6 @@ module.exports = {
},
create(context) {
const MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.";
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_FUNCTION_BODY_INDENT = 1;
@ -192,108 +190,91 @@ module.exports = {
}
}
const indentPattern = {
normal: indentType === "space" ? /^ +/ : /^\t+/,
excludeCommas: indentType === "space" ? /^[ ,]+/ : /^[\t,]+/
};
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 {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 {boolean} isLastNodeCheck Is the error for last node check
* @returns {void}
*/
function report(node, needed, gotten, 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 = [];
function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
if (needed > gotten) {
const spaces = indentChar.repeat(needed - gotten);
if (gottenSpaces && gottenTabs) {
if (isLastNodeCheck === true) {
rangeToFix = [
node.range[1] - 1,
node.range[1] - 1
];
} else {
rangeToFix = [
node.range[0],
node.range[0]
];
// To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
return;
}
return function(fixer) {
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]
];
}
const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
return function(fixer) {
return fixer.removeRange(rangeToFix);
};
}
}
const textRange = isLastNodeCheck
? [node.range[1] - gottenSpaces - gottenTabs - 1, node.range[1] - 1]
: [node.range[0] - gottenSpaces - gottenTabs, node.range[0]];
if (loc) {
context.report({
node,
loc,
message: MESSAGE,
data: msgContext,
fix: getFixerFunction()
});
} else {
context.report({
node,
message: MESSAGE,
data: msgContext,
fix: getFixerFunction()
message: createErrorMessage(needed, gottenSpaces, gottenTabs),
fix: fixer => fixer.replaceTextRange(textRange, desiredIndent)
});
}
}
/**
* Get the actual indent of node
* @param {ASTNode|Token} node Node to examine
* @param {boolean} [byLastLine=false] get indent of node's last line
* @param {boolean} [excludeCommas=false] skip comma on start of line
* @returns {int} Indent
* @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 src = sourceCode.getText(token, token.loc.start.column);
const regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal;
const indent = regExp.exec(src);
const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split("");
const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t"));
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
* @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
* @returns {void}
*/
function checkNodeIndent(node, indent, excludeCommas) {
const nodeIndent = getNodeIndent(node, false, excludeCommas);
function checkNodeIndent(node, neededIndent) {
const actualIndent = getNodeIndent(node, false);
if (
node.type !== "ArrayExpression" && node.type !== "ObjectExpression" &&
nodeIndent !== indent && isNodeFirstInLine(node)
node.type !== "ArrayExpression" &&
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) {
const elseToken = sourceCode.getTokenBefore(node.alternate);
checkNodeIndent(elseToken, indent, excludeCommas);
checkNodeIndent(elseToken, neededIndent);
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
* @returns {void}
*/
function checkNodesIndent(nodes, indent, excludeCommas) {
nodes.forEach(node => checkNodeIndent(node, indent, excludeCommas));
function checkNodesIndent(nodes, indent) {
nodes.forEach(node => checkNodeIndent(node, indent));
}
/**
@ -359,11 +342,12 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node);
const endIndent = getNodeIndent(lastToken, true);
if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) {
if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) {
report(
node,
lastLineIndent,
endIndent,
endIndent.space,
endIndent.tab,
{ line: lastToken.loc.start.line, column: lastToken.loc.start.column },
true
);
@ -379,11 +363,12 @@ module.exports = {
function checkFirstNodeLineIndent(node, firstLineIndent) {
const startIndent = getNodeIndent(node, false);
if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) {
if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) {
report(
node,
firstLineIndent,
startIndent,
startIndent.space,
startIndent.tab,
{ line: node.loc.start.line, column: node.loc.start.column }
);
}
@ -526,11 +511,11 @@ module.exports = {
calleeNode.parent.type === "ArrayExpression")) {
// 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 {
// If function is standalone, simple calculate indent
indent = getNodeIndent(calleeNode);
indent = getNodeIndent(calleeNode).goodChar;
}
if (calleeNode.parent.type === "CallExpression") {
@ -538,13 +523,13 @@ module.exports = {
if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") {
if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) {
indent = getNodeIndent(calleeParent);
indent = getNodeIndent(calleeParent).goodChar;
}
} else {
if (isArgBeforeCalleeNodeMultiline(calleeNode) &&
calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line &&
!isNodeFirstInLine(calleeNode)) {
indent = getNodeIndent(calleeParent);
indent = getNodeIndent(calleeParent).goodChar;
}
}
}
@ -644,7 +629,7 @@ module.exports = {
effectiveParent = parent.parent;
}
}
nodeIndent = getNodeIndent(effectiveParent);
nodeIndent = getNodeIndent(effectiveParent).goodChar;
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) {
@ -654,7 +639,8 @@ module.exports = {
parent.type === "ArrayExpression" ||
parent.type === "CallExpression" ||
parent.type === "ArrowFunctionExpression" ||
parent.type === "NewExpression"
parent.type === "NewExpression" ||
parent.type === "LogicalExpression"
) {
nodeIndent = nodeIndent + indentSize;
}
@ -667,7 +653,7 @@ module.exports = {
checkFirstNodeLineIndent(node, nodeIndent);
} else {
nodeIndent = getNodeIndent(node);
nodeIndent = getNodeIndent(node).goodChar;
elementsIndent = nodeIndent + indentSize;
}
@ -679,8 +665,7 @@ module.exports = {
elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
}
// Comma can be placed before property name
checkNodesIndent(elements, elementsIndent, true);
checkNodesIndent(elements, elementsIndent);
if (elements.length > 0) {
@ -736,9 +721,9 @@ module.exports = {
];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
indent = getNodeIndent(node.parent);
indent = getNodeIndent(node.parent).goodChar;
} else {
indent = getNodeIndent(node);
indent = getNodeIndent(node).goodChar;
}
if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") {
@ -784,13 +769,12 @@ module.exports = {
*/
function checkIndentInVariableDeclarations(node) {
const elements = filterOutSameLineVars(node);
const nodeIndent = getNodeIndent(node);
const nodeIndent = getNodeIndent(node).goodChar;
const lastElement = elements[elements.length - 1];
const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind];
// Comma can be placed before declaration
checkNodesIndent(elements, elementsIndent, true);
checkNodesIndent(elements, elementsIndent);
// 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) {
@ -802,7 +786,7 @@ module.exports = {
if (tokenBeforeLastElement.value === ",") {
// Special case for comma-first syntax where the semicolon is indented
checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement));
checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar);
} else {
checkLastNodeLineIndent(node, elementsIndent - indentSize);
}
@ -834,7 +818,7 @@ module.exports = {
return caseIndentStore[switchNode.loc.start.line];
} else {
if (typeof switchIndent === "undefined") {
switchIndent = getNodeIndent(switchNode);
switchIndent = getNodeIndent(switchNode).goodChar;
}
if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
@ -853,7 +837,7 @@ module.exports = {
if (node.body.length > 0) {
// Root nodes should have no indent
checkNodesIndent(node.body, getNodeIndent(node));
checkNodesIndent(node.body, getNodeIndent(node).goodChar);
}
},
@ -912,7 +896,7 @@ module.exports = {
return;
}
const propertyIndent = getNodeIndent(node) + indentSize * options.MemberExpression;
const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression;
const checkNodes = [node.property];
@ -928,7 +912,7 @@ module.exports = {
SwitchStatement(node) {
// Switch is not a 'BlockStatement'
const switchIndent = getNodeIndent(node);
const switchIndent = getNodeIndent(node).goodChar;
const caseIndent = expectedCaseIndent(node, switchIndent);
checkNodesIndent(node.cases, caseIndent);
@ -955,7 +939,7 @@ module.exports = {
if (options.FunctionDeclaration.parameters === "first" && node.params.length) {
checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
} 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) {
checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
} 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 = {
meta: {
docs: {
description: "require or disallow initialization in `var` declarations",
description: "require or disallow initialization in variable declarations",
category: "Variables",
recommended: false
},

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

@ -23,7 +23,7 @@ const NEXT_TOKEN_M = /^[\{*]$/;
const TEMPLATE_OPEN_PAREN = /\$\{$/;
const TEMPLATE_CLOSE_PAREN = /^\}/;
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.
(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
* spacing around those keywords is invalid.
@ -482,7 +499,13 @@ module.exports = {
if (node.static) {
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(
node,
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 {
// Statements
@ -522,13 +556,15 @@ module.exports = {
ExportNamedDeclaration: checkSpacingForModuleDeclaration,
ExportDefaultDeclaration: checkSpacingAroundFirstToken,
ExportAllDeclaration: checkSpacingForModuleDeclaration,
FunctionDeclaration: checkSpacingBeforeFirstToken,
FunctionDeclaration: checkSpacingForFunction,
ImportDeclaration: checkSpacingForModuleDeclaration,
VariableDeclaration: checkSpacingAroundFirstToken,
// Expressions
ArrowFunctionExpression: checkSpacingForFunction,
AwaitExpression: checkSpacingForAwaitExpression,
ClassExpression: checkSpacingForClass,
FunctionExpression: checkSpacingBeforeFirstToken,
FunctionExpression: checkSpacingForFunction,
NewExpression: checkSpacingBeforeFirstToken,
Super: checkSpacingBeforeFirstToken,
ThisExpression: checkSpacingBeforeFirstToken,

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

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

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

@ -37,7 +37,8 @@ module.exports = {
minProperties: 2
}
]
}]
}],
fixable: "whitespace"
},
create(context) {
@ -88,6 +89,12 @@ module.exports = {
expected: expected ? "Expected" : "Unexpected",
value: node.expression.value,
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
});
return line.length + extraCharacterCount;
return Array.from(line).length + extraCharacterCount;
}
// The options object must be the last option specified…
@ -235,8 +235,9 @@ module.exports = {
* @private
*/
function groupByLineNumber(acc, node) {
ensureArrayAndPush(acc, node.loc.start.line, node);
ensureArrayAndPush(acc, node.loc.end.line, node);
for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) {
ensureArrayAndPush(acc, i, node);
}
return acc;
}

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

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

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

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

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

@ -12,7 +12,7 @@
module.exports = {
meta: {
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",
recommended: false
},

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

@ -75,7 +75,7 @@ function calculateCapIsNewExceptions(config) {
module.exports = {
meta: {
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",
recommended: false
},

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

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

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

@ -22,7 +22,9 @@ module.exports = {
recommended: false
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
@ -39,7 +41,14 @@ module.exports = {
context.report({
node: node.parent.parent,
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]]);
}
});
}

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

@ -367,17 +367,17 @@ module.exports = {
* @private
*/
function dryBinaryLogical(node) {
if (!NESTED_BINARY) {
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);
}
if (hasExcessParens(node.right) && precedence(node.right) > prec) {
if (!shouldSkipRight && hasExcessParens(node.right) && precedence(node.right) > prec) {
report(node.right);
}
}
}
return {
ArrayExpression(node) {
@ -587,6 +587,7 @@ module.exports = {
UnaryExpression: dryUnaryUpdate,
UpdateExpression: dryUnaryUpdate,
AwaitExpression: dryUnaryUpdate,
VariableDeclarator(node) {
if (node.init && hasExcessParens(node.init) &&

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

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

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

@ -12,7 +12,7 @@
module.exports = {
meta: {
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",
recommended: false
},

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

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

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

@ -16,10 +16,13 @@ module.exports = {
recommended: false
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
const sourceCode = context.getSourceCode();
return {
IfStatement(node) {
@ -31,7 +34,46 @@ module.exports = {
parent.body.length === 1 && grandparent &&
grandparent.type === "IfStatement" &&
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 = {
meta: {
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",
recommended: false
},

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

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

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

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

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

@ -5,6 +5,8 @@
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -17,7 +19,9 @@ module.exports = {
recommended: true
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
@ -27,19 +31,27 @@ module.exports = {
* Validate regular expressions
* @param {ASTNode} node node 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}
* @private
*/
function checkRegex(node, value) {
function checkRegex(node, value, valueStart) {
const multipleSpacesRegex = /( {2,})+?/,
regexResults = multipleSpacesRegex.exec(value);
if (regexResults !== null) {
const count = regexResults[0].length;
context.report({
node,
message: "Spaces are hard to count. Use {{{count}}}.",
data: {
count: regexResults[0].length
data: {count},
fix(fixer) {
return fixer.replaceTextRange(
[valueStart + regexResults.index, valueStart + regexResults.index + count],
` {${count}}`
);
}
});
@ -62,7 +74,7 @@ module.exports = {
nodeValue = token.value;
if (nodeType === "RegularExpression") {
checkRegex(node, nodeValue);
checkRegex(node, nodeValue, token.start);
}
}
@ -83,8 +95,12 @@ module.exports = {
* @private
*/
function checkFunction(node) {
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) {
checkRegex(node, node.arguments[0].value);
const scope = context.getScope();
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);
}
}

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

@ -15,13 +15,15 @@ module.exports = {
meta: {
docs: {
description: "disallow certain properties on certain objects",
category: "Node.js and CommonJS",
category: "Best Practices",
recommended: false
},
schema: {
type: "array",
items: {
anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided.
{
type: "object",
properties: {
object: {
@ -35,9 +37,24 @@ module.exports = {
}
},
additionalProperties: false,
required: [
"object",
"property"
required: ["object"]
},
{
type: "object",
properties: {
object: {
type: "string"
},
property: {
type: "string"
},
message: {
type: "string"
}
},
additionalProperties: false,
required: ["property"]
}
]
},
uniqueItems: true
@ -51,38 +68,96 @@ module.exports = {
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 propertyName = option.property;
if (!restrictions.has(objectName)) {
restrictions.set(objectName, new Map());
if (typeof objectName === "undefined") {
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());
}
restrictions.get(objectName).set(propertyName, {
restrictedProperties.get(objectName).set(propertyName, {
message: option.message
});
}
});
return restrictions;
}, new Map());
return {
MemberExpression(node) {
const objectName = node.object && node.object.name;
const propertyName = astUtils.getStaticPropertyName(node);
/**
* 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);
const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName);
const globalMatchedProperty = globallyRestrictedProperties.get(propertyName);
if (matchedObjectProperty) {
const message = matchedObjectProperty.message ? " " + matchedObjectProperty.message : "";
const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
context.report(node, "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", {
objectName,
propertyName,
message
});
} else if (globalMatchedProperty) {
const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";
context.report(node, "'{{propertyName}}' is restricted from being used.{{message}}", {
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 = {
meta: {
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",
recommended: false
},

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

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

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

@ -5,6 +5,8 @@
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -17,19 +19,38 @@ module.exports = {
recommended: false
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
const sourceCode = context.getSourceCode();
return {
VariableDeclarator(node) {
const name = node.id.name,
init = node.init && node.init.name;
if (init === "undefined" && node.parent.kind !== "const") {
context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name });
const name = sourceCode.getText(node.id),
init = node.init && node.init.name,
scope = context.getScope(),
undefinedVar = astUtils.getVariableByName(scope, "undefined"),
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);
}

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

@ -60,7 +60,8 @@ module.exports = {
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 = {
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.
* @param {Scope} scope - an escope Scope object.
@ -466,7 +494,7 @@ module.exports = {
if (type === "Parameter") {
// 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;
}
@ -481,7 +509,7 @@ module.exports = {
}
// 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;
}
} else {
@ -569,13 +597,13 @@ module.exports = {
context.report({
node: programNode,
loc: getLocation(unusedVar),
message: MESSAGE,
message: DEFINED_MESSAGE,
data: unusedVar
});
} else if (unusedVar.defs.length > 0) {
context.report({
node: unusedVar.identifiers[0],
message: MESSAGE,
message: unusedVar.references.some(ref => ref.isWrite()) ? ASSIGNED_MESSAGE : DEFINED_MESSAGE,
data: unusedVar
});
}

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

@ -18,7 +18,9 @@ module.exports = {
recommended: false
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
const sourceCode = context.getSourceCode();
@ -33,7 +35,24 @@ module.exports = {
nodeType = typeof key.value;
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
* @param {string[]} escapes - list of valid escapes
* @param {ASTNode} node - node to validate.
* @param {string} elm - string slice to validate.
* @param {string} match - string slice to validate.
* @returns {void}
*/
function validate(escapes, node, elm) {
const escapeNotFound = escapes.indexOf(elm[0][1]) === -1;
const isQuoteEscape = elm[0][1] === node.raw[0];
function validate(escapes, node, match) {
const isTemplateElement = node.type === "TemplateElement";
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({
node,
loc: {
line: node.loc.start.line,
column: node.loc.start.column + elm.index
column: node.loc.start.column + match.index
},
message: "Unnecessary escape character: {{character}}.",
data: {
character: elm[0]
character: match[0]
}
});
}
@ -105,12 +126,18 @@ module.exports = {
* @returns {void}
*/
function check(node) {
let nodeEscapes, match;
const isTemplateElement = node.type === "TemplateElement";
const value = isTemplateElement ? node.value.raw : node.raw;
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") {
return;
}
@ -122,12 +149,14 @@ module.exports = {
return;
}
while ((match = pattern.exec(node.raw))) {
while ((match = pattern.exec(value))) {
validate(nodeEscapes, node, match);
}
}
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)
},
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);
}
});

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

@ -14,6 +14,11 @@ const OPTIONS = {
consistentAsNeeded: "consistent-as-needed"
};
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// 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
* @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
**/
function isNotGetterOrSetter(property) {
return (property.kind !== "set" && property.kind !== "get");
function canHaveShorthand(property) {
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.
* @private
**/
function isRedudant(property) {
return (property.key && (
function isRedundant(property) {
const value = property.value;
// A function expression
property.value && property.value.id && property.value.id.name === property.key.name ||
if (value.type === "FunctionExpression") {
return !value.id; // Only anonymous should be shorthand method.
}
if (value.type === "Identifier") {
return astUtils.getStaticPropertyName(property) === value.name;
}
// A property
property.value && property.value.name === property.key.name
));
return false;
}
/**
@ -168,8 +175,8 @@ module.exports = {
**/
function checkConsistency(node, checkRedundancy) {
// We are excluding getters and setters as they are considered neither longform nor shorthand.
const properties = node.properties.filter(isNotGetterOrSetter);
// We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand.
const properties = node.properties.filter(canHaveShorthand);
// Do we still have properties left after filtering the getters and setters?
if (properties.length > 0) {
@ -185,8 +192,8 @@ module.exports = {
} else if (checkRedundancy) {
// If all properties of the object contain a method or value with a name matching it's key,
// all the keys are redudant.
const canAlwaysUseShorthand = properties.every(isRedudant);
// all the keys are redundant.
const canAlwaysUseShorthand = properties.every(isRedundant);
if (canAlwaysUseShorthand) {
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 = {
meta: {
docs: {
description: "require or disallow newlines around `var` declarations",
description: "require or disallow newlines around variable declarations",
category: "Stylistic Issues",
recommended: false
},
@ -20,7 +20,9 @@ module.exports = {
{
enum: ["always", "initializations"]
}
]
],
fixable: "whitespace"
},
create(context) {
@ -63,7 +65,8 @@ module.exports = {
context.report({
node,
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 paramsRightParen = sourceCode.getTokenBefore(node.body);
const asyncKeyword = node.async ? "async " : "";
const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
if (callbackInfo.isLexicalThis) {
// 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.
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
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
@ -27,6 +29,12 @@ module.exports = {
16: "hexadecimal"
};
const prefixMap = {
2: "0b",
8: "0o",
16: "0x"
};
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@ -53,6 +61,17 @@ module.exports = {
message: "Use {{radixName}} literals instead of parseInt().",
data: {
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.computed === false &&
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
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
@ -95,7 +98,21 @@ module.exports = {
const thisArg = node.arguments[0];
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;
}
/**
* 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.
* @param {ASTNode} node - A node to check.
@ -50,6 +64,36 @@ function hasNonStringLiteral(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
//------------------------------------------------------------------------------
@ -62,12 +106,86 @@ module.exports = {
recommended: false
},
schema: []
schema: [],
fixable: "code"
},
create(context) {
const sourceCode = context.getSourceCode();
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.
*
@ -88,9 +206,13 @@ module.exports = {
done[topBinaryExpr.range[0]] = true;
if (hasNonStringLiteral(topBinaryExpr)) {
context.report(
topBinaryExpr,
"Unexpected string concatenation.");
context.report({
node: topBinaryExpr,
message: "Unexpected string concatenation.",
fix(fixer) {
return fixer.replaceText(topBinaryExpr, getTemplateLiteral(topBinaryExpr, null, null));
}
});
}
}

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

@ -61,7 +61,9 @@ module.exports = {
maxItems: 2
}
]
}
},
fixable: "code"
},
create(context) {
@ -74,7 +76,8 @@ module.exports = {
MESSAGE_UNNECESSARY = "Unnecessarily quoted property '{{property}}' found.",
MESSAGE_UNQUOTED = "Unquoted property '{{property}}' found.",
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));
}
/**
* 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
* @param {ASTNode} node Property AST node
@ -131,12 +159,27 @@ module.exports = {
}
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)) {
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") {
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;
if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) {
context.report(node, MESSAGE_UNQUOTED, {
property: key.name || key.value
context.report({
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}
*/
function checkConsistency(node, checkQuotesRedundancy) {
let quotes = false,
lackOfQuotes = false,
const quotedProps = [],
unquotedProps = [];
let keywordKeyName = null,
necessaryQuotes = false;
node.properties.forEach(function(property) {
@ -176,7 +223,7 @@ module.exports = {
if (key.type === "Literal" && typeof key.value === "string") {
quotes = true;
quotedProps.push(property);
if (checkQuotesRedundancy) {
try {
@ -189,21 +236,40 @@ module.exports = {
necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value);
}
} else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) {
unquotedProps.push(property);
necessaryQuotes = true;
context.report(node, "Properties should be quoted as '{{property}}' is a reserved word.", {property: key.name});
keywordKeyName = key.name;
} else {
lackOfQuotes = true;
unquotedProps.push(property);
}
});
if (quotes && lackOfQuotes) {
context.report(node, "Inconsistently quoted property '{{key}}' found.", {
key: key.name || key.value
if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) {
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))
});
}
});
if (checkQuotesRedundancy && quotes && !necessaryQuotes) {
context.report(node, "Properties shouldn't be quoted as all quotes are redundant.");
}
}

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

@ -123,12 +123,26 @@ module.exports = {
/**
* 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
*/
function isJSXElement(node) {
return node.type.indexOf("JSX") === 0;
function isJSXLiteral(node) {
return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement";
}
/**
@ -215,7 +229,7 @@ module.exports = {
if (settings && typeof val === "string") {
isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
isJSXElement(node.parent) ||
isJSXLiteral(node) ||
astUtils.isSurroundedBy(rawVal, settings.quote);
if (!isValid && avoidEscape) {

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

@ -53,7 +53,7 @@ module.exports = {
create(context) {
const OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+-
const OPT_OUT_PATTERN = /^[-[(\/+]$/; // One of [(/+-, but not ++ or --
const options = context.options[1];
const never = context.options[0] === "never",
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
*/
@ -74,7 +74,7 @@ const isValidOrders = {
module.exports = {
meta: {
docs: {
description: "requires object keys to be sorted",
description: "require object keys to be sorted",
category: "Stylistic Issues",
recommended: false
},

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

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

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

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

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

@ -176,6 +176,17 @@ module.exports = {
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
* @param {ASTnode} node AST node
@ -291,7 +302,8 @@ module.exports = {
UnaryExpression: checkForSpaces,
UpdateExpression: 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"]
}
]
],
fixable: "code"
},
create(context) {
@ -104,40 +106,59 @@ module.exports = {
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.
* @param {ASTNode[]} nodes Nodes.
* @param {string} start Index to start from.
* @param {string} end Index to end before.
* @param {string} message Message to display.
* @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
* @returns {void}
*/
function reportSlice(nodes, start, end, message) {
let i;
for (i = start; i < end; i++) {
context.report(nodes[i], message);
}
function reportSlice(nodes, start, end, message, fix) {
nodes.slice(start, end).forEach(node => {
context.report({node, message, fix: fix ? getFixFunction(node) : null});
});
}
/**
* Report all nodes in an array with a given message.
* @param {ASTNode[]} nodes Nodes.
* @param {string} message Message to display.
* @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
* @returns {void}
*/
function reportAll(nodes, message) {
reportSlice(nodes, 0, nodes.length, message);
function reportAll(nodes, message, fix) {
reportSlice(nodes, 0, nodes.length, message, fix);
}
/**
* Report all nodes in an array, except the first, with a given message.
* @param {ASTNode[]} nodes Nodes.
* @param {string} message Message to display.
* @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
* @returns {void}
*/
function reportAllExceptFirst(nodes, message) {
reportSlice(nodes, 1, nodes.length, message);
function reportAllExceptFirst(nodes, message, fix) {
reportSlice(nodes, 1, nodes.length, message, fix);
}
/**
@ -157,12 +178,12 @@ module.exports = {
if (!isSimpleParameterList(node.params)) {
context.report(useStrictDirectives[0], messages.nonSimpleParameterList);
} else if (isParentStrict) {
context.report(useStrictDirectives[0], messages.unnecessary);
context.report({node: useStrictDirectives[0], message: messages.unnecessary, fix: getFixFunction(useStrictDirectives[0])});
} 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) {
if (isSimpleParameterList(node.params)) {
context.report(node, messages.function);
@ -198,10 +219,10 @@ module.exports = {
enterFunctionInFunctionMode(node, useStrictDirectives);
} else if (useStrictDirectives.length > 0) {
if (isSimpleParameterList(node.params)) {
reportAll(useStrictDirectives, messages[mode]);
reportAll(useStrictDirectives, messages[mode], shouldFix(mode));
} else {
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) {
context.report(node, messages.global);
}
reportAllExceptFirst(useStrictDirectives, messages.multiple);
reportAllExceptFirst(useStrictDirectives, messages.multiple, true);
} else {
reportAll(useStrictDirectives, messages[mode]);
reportAll(useStrictDirectives, messages[mode], shouldFix(mode));
}
},
FunctionDeclaration: enterFunction,

4
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} type JSDoc tag
* @returns {void}
@ -192,7 +192,9 @@ module.exports = {
elements = type.elements;
break;
case "FieldType": // Array.<{count: number, votes: number}>
if (type.value) {
typesToCheck.push(getCurrentExpectedTypes(type.value));
}
break;
default:
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"],
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
//--------------------------------------------------------------------------
@ -41,17 +52,19 @@ module.exports = {
return {
UnaryExpression(node) {
if (node.operator === "typeof") {
if (isTypeofExpression(node)) {
const parent = context.getAncestors().pop();
if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) {
const sibling = parent.left === node ? parent.right : parent.left;
if (sibling.type === "Literal") {
if (VALID_TYPES.indexOf(sibling.value) === -1) {
if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) {
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.");
}
} else if (context.options[0] && context.options[0].requireStringLiterals) {
} else if (requireStringLiterals && !isTypeofExpression(sibling)) {
context.report(sibling, "Typeof comparisons should be to string literals.");
}
}

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

@ -5,6 +5,8 @@
"use strict";
const astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -20,13 +22,25 @@ module.exports = {
schema: [
{
enum: ["outside", "inside", "any"]
},
{
type: "object",
properties: {
functionPrototypeMethods: {
type: "boolean"
}
},
additionalProperties: false
}
]
],
fixable: "code"
},
create(context) {
const style = context.options[0] || "outside";
const includeFunctionPrototypeMethods = (context.options[1] && context.options[1].functionPrototypeMethods) || false;
const sourceCode = context.getSourceCode();
@ -44,20 +58,91 @@ module.exports = {
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) {
if (node.callee.type === "FunctionExpression") {
const innerNode = getFunctionNodeFromIIFE(node);
if (!innerNode) {
return;
}
const callExpressionWrapped = wrapped(node),
functionExpressionWrapped = wrapped(node.callee);
functionExpressionWrapped = wrapped(innerNode);
if (!callExpressionWrapped && !functionExpressionWrapped) {
context.report(node, "Wrap an immediate function invocation in parentheses.");
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, "Wrap only the function expression in parens.");
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, "Move the invocation into the parens that contain the function.");
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
}
]
],
fixable: "code"
},
create(context) {
@ -219,46 +221,57 @@ module.exports = {
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
//--------------------------------------------------------------------------
return {
BinaryExpression: always ? function(node) {
// Comparisons must always be yoda-style: if ("blue" === color)
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) {
BinaryExpression(node) {
const expectedLiteral = always ? node.left : node.right;
const expectedNonLiteral = always ? node.right : node.left;
// Comparisons must never be yoda-style (default)
// If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
if (
(node.left.type === "Literal" || looksLikeLiteral(node.left)) &&
!(node.right.type === "Literal" || looksLikeLiteral(node.right)) &&
(expectedNonLiteral.type === "Literal" || looksLikeLiteral(expectedNonLiteral)) &&
!(expectedLiteral.type === "Literal" || looksLikeLiteral(expectedLiteral)) &&
!(!isEqualityOperator(node.operator) && onlyEquality) &&
isComparisonOperator(node.operator) &&
!(exceptRange && isRangeTest(context.getAncestors().pop()))
) {
context.report({
node,
message: "Expected literal to be on the right side of {{operator}}.",
message: "Expected literal to be on the {{expectedSide}} side of {{operator}}.",
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
RuleTester.describe = (typeof describe === "function") ? describe : /* istanbul ignore next */ function(text, method) {
return method.apply(this);
};
const DESCRIBE = Symbol("describe");
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);
};
}
// 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 = {
@ -266,9 +306,9 @@ RuleTester.prototype = {
if (validateSchema.errors) {
throw new Error([
"Schema for rule " + ruleName + " is invalid:"
`Schema for rule ${ruleName} is invalid:`
].concat(validateSchema.errors.map(function(error) {
return "\t" + error.field + ": " + error.message;
return `\t${error.field}: ${error.message}`;
})).join("\n"));
}
}
@ -373,7 +413,7 @@ RuleTester.prototype = {
*/
function testInvalidTemplate(ruleName, item) {
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 messages = result.messages;
@ -389,7 +429,7 @@ RuleTester.prototype = {
item.errors.length, item.errors.length === 1 ? "" : "s", messages.length, util.inspect(messages)));
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");
if (typeof item.errors[i] === "string") {
@ -408,23 +448,23 @@ RuleTester.prototype = {
}
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")) {
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")) {
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")) {
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")) {
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 {

2
tools/eslint/lib/timing.js

@ -66,7 +66,7 @@ function display(data) {
.slice(0, 10);
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);
});

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

@ -50,9 +50,9 @@ function processPath(options) {
let suffix = "/**";
if (extensions.length === 1) {
suffix += "/*." + extensions[0];
suffix += `/*.${extensions[0]}`;
} 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
if (!result) {
throw new Error("Cannot find module '" + name + "'");
throw new Error(`Cannot find module '${name}'`);
}
return result;

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

@ -46,7 +46,7 @@ NodeEventGenerator.prototype = {
* @returns {void}
*/
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)) {
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