diff --git a/bin/eslint.js b/bin/eslint.js
index 86291b0f527..5fa5766828e 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -66,11 +66,8 @@ function readStdin() {
*/
function getErrorMessage(error) {
- // Lazy loading because those are used only if error happened.
- const fs = require("fs");
- const path = require("path");
+ // Lazy loading because this is used only if an error happened.
const util = require("util");
- const lodash = require("lodash");
// Foolproof -- thirdparty module might throw non-object.
if (typeof error !== "object" || error === null) {
@@ -80,14 +77,7 @@ function getErrorMessage(error) {
// Use templates if `error.messageTemplate` is present.
if (typeof error.messageTemplate === "string") {
try {
- const templateFilePath = path.resolve(
- __dirname,
- `../messages/${error.messageTemplate}.txt`
- );
-
- // Use sync API because Node.js should exit at this tick.
- const templateText = fs.readFileSync(templateFilePath, "utf-8");
- const template = lodash.template(templateText);
+ const template = require(`../messages/${error.messageTemplate}.js`);
return template(error.messageData || {});
} catch {
diff --git a/docs/developer-guide/code-path-analysis.md b/docs/developer-guide/code-path-analysis.md
index 7762bc5d652..cd3fc29365a 100644
--- a/docs/developer-guide/code-path-analysis.md
+++ b/docs/developer-guide/code-path-analysis.md
@@ -195,8 +195,6 @@ bar();
### To check whether or not this is reachable
```js
-var last = require("lodash").last;
-
function isReachable(segment) {
return segment.reachable;
}
@@ -215,7 +213,7 @@ module.exports = function(context) {
// Checks reachable or not.
"ExpressionStatement": function(node) {
- var codePath = last(codePathStack);
+ var codePath = codePathStack[codePathStack.length - 1];
// Checks the current code path segments.
if (!codePath.currentSegments.some(isReachable)) {
@@ -239,8 +237,6 @@ So a rule must not modify those instances.
Please use a map of information instead.
```js
-var last = require("lodash").last;
-
function hasCb(node, context) {
if (node.type.indexOf("Function") !== -1) {
return context.getDeclaredVariables(node).some(function(v) {
@@ -285,8 +281,10 @@ module.exports = function(context) {
// Manages state of code paths.
"onCodePathSegmentStart": function(segment) {
+ var funcInfo = funcInfoStack[funcInfoStack.length - 1];
+
// Ignores if `cb` doesn't exist.
- if (!last(funcInfoStack).hasCb) {
+ if (!funcInfo.hasCb) {
return;
}
@@ -304,7 +302,7 @@ module.exports = function(context) {
// Checks reachable or not.
"CallExpression": function(node) {
- var funcInfo = last(funcInfoStack);
+ var funcInfo = funcInfoStack[funcInfoStack.length - 1];
// Ignores if `cb` doesn't exist.
if (!funcInfo.hasCb) {
diff --git a/docs/developer-guide/code-path-analysis/README.md b/docs/developer-guide/code-path-analysis/README.md
index c283d51bf91..1c84b2e1f73 100644
--- a/docs/developer-guide/code-path-analysis/README.md
+++ b/docs/developer-guide/code-path-analysis/README.md
@@ -195,8 +195,6 @@ bar();
### To check whether or not this is reachable
```js
-var last = require("lodash").last;
-
function isReachable(segment) {
return segment.reachable;
}
@@ -215,7 +213,7 @@ module.exports = function(context) {
// Checks reachable or not.
"ExpressionStatement": function(node) {
- var codePath = last(codePathStack);
+ var codePath = codePathStack[codePathStack.length - 1];
// Checks the current code path segments.
if (!codePath.currentSegments.some(isReachable)) {
@@ -239,8 +237,6 @@ So a rule must not modify those instances.
Please use a map of information instead.
```js
-var last = require("lodash").last;
-
function hasCb(node, context) {
if (node.type.indexOf("Function") !== -1) {
return context.getDeclaredVariables(node).some(function(v) {
@@ -285,8 +281,10 @@ module.exports = function(context) {
// Manages state of code paths.
"onCodePathSegmentStart": function(segment) {
+ var funcInfo = funcInfoStack[funcInfoStack - 1];
+
// Ignores if `cb` doesn't exist.
- if (!last(funcInfoStack).hasCb) {
+ if (!funcInfo.hasCb) {
return;
}
@@ -304,7 +302,7 @@ module.exports = function(context) {
// Checks reachable or not.
"CallExpression": function(node) {
- var funcInfo = last(funcInfoStack);
+ var funcInfo = funcInfoStack[funcInfoStack - 1];
// Ignores if `cb` doesn't exist.
if (!funcInfo.hasCb) {
diff --git a/lib/cli-engine/file-enumerator.js b/lib/cli-engine/file-enumerator.js
index bd89ec7334c..ade28517b42 100644
--- a/lib/cli-engine/file-enumerator.js
+++ b/lib/cli-engine/file-enumerator.js
@@ -38,7 +38,7 @@ const fs = require("fs");
const path = require("path");
const getGlobParent = require("glob-parent");
const isGlob = require("is-glob");
-const { escapeRegExp } = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
const { Minimatch } = require("minimatch");
const {
diff --git a/lib/cli-engine/formatters/html-template-message.html b/lib/cli-engine/formatters/html-template-message.html
deleted file mode 100644
index 93795a1bdc8..00000000000
--- a/lib/cli-engine/formatters/html-template-message.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
+
ESLint Report
- <%= reportSummary %> - Generated on <%= date %>
+ ${reportSummary} - Generated on ${date}
- <%= results %>
+ ${results}
+`.trimLeft();
+};
diff --git a/lib/cli-engine/formatters/html-template-result.html b/lib/cli-engine/formatters/html-template-result.html
deleted file mode 100644
index f4a55933c20..00000000000
--- a/lib/cli-engine/formatters/html-template-result.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- [+] <%- filePath %>
- <%- summary %>
- |
-
diff --git a/lib/cli-engine/formatters/html-template-result.js b/lib/cli-engine/formatters/html-template-result.js
new file mode 100644
index 00000000000..5048f72e928
--- /dev/null
+++ b/lib/cli-engine/formatters/html-template-result.js
@@ -0,0 +1,14 @@
+"use strict";
+
+module.exports = function(it, encodeHTML) {
+ const { color, index, filePath, summary } = it;
+
+ return `
+
+
+ [+] ${encodeHTML(filePath)}
+ ${encodeHTML(summary)}
+ |
+
+`.trimLeft();
+};
diff --git a/lib/cli-engine/formatters/html.js b/lib/cli-engine/formatters/html.js
index 5d4b7e56060..b9739f05e2d 100644
--- a/lib/cli-engine/formatters/html.js
+++ b/lib/cli-engine/formatters/html.js
@@ -4,17 +4,30 @@
*/
"use strict";
-const lodash = require("lodash");
-const fs = require("fs");
-const path = require("path");
-
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
-const pageTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-page.html"), "utf-8"));
-const messageTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-message.html"), "utf-8"));
-const resultTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-result.html"), "utf-8"));
+const encodeHTML = (function() {
+ const encodeHTMLRules = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'"
+ };
+ const matchHTML = /[&<>"']/ug;
+
+ return function(code) {
+ return code
+ ? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m)
+ : "";
+ };
+}());
+
+const pageTemplate = require("./html-template-page.js");
+const messageTemplate = require("./html-template-message.js");
+const resultTemplate = require("./html-template-result.js");
/**
* Given a word and a count, append an s if count is not one.
@@ -80,7 +93,9 @@ function renderMessages(messages, parentIndex, rulesMeta) {
if (rulesMeta) {
const meta = rulesMeta[message.ruleId];
- ruleUrl = lodash.get(meta, "docs.url", null);
+ if (meta && meta.docs && meta.docs.url) {
+ ruleUrl = meta.docs.url;
+ }
}
return messageTemplate({
@@ -92,7 +107,7 @@ function renderMessages(messages, parentIndex, rulesMeta) {
message: message.message,
ruleId: message.ruleId,
ruleUrl
- });
+ }, encodeHTML);
}).join("\n");
}
@@ -108,8 +123,7 @@ function renderResults(results, rulesMeta) {
color: renderColor(result.errorCount, result.warningCount),
filePath: result.filePath,
summary: renderSummary(result.errorCount, result.warningCount)
-
- }) + renderMessages(result.messages, index, rulesMeta)).join("\n");
+ }, encodeHTML) + renderMessages(result.messages, index, rulesMeta)).join("\n");
}
//------------------------------------------------------------------------------
diff --git a/lib/init/autoconfig.js b/lib/init/autoconfig.js
index 19d016a54c1..054c538496f 100644
--- a/lib/init/autoconfig.js
+++ b/lib/init/autoconfig.js
@@ -9,7 +9,7 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash"),
+const equal = require("fast-deep-equal"),
recConfig = require("../../conf/eslint-recommended"),
ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
{ Linter } = require("../linter"),
@@ -329,7 +329,7 @@ function extendFromRecommended(config) {
const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
recRules.forEach(ruleId => {
- if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
+ if (equal(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
delete newConfig.rules[ruleId];
}
});
diff --git a/lib/linter/apply-disable-directives.js b/lib/linter/apply-disable-directives.js
index 41d6934abba..0ba69ca9cc4 100644
--- a/lib/linter/apply-disable-directives.js
+++ b/lib/linter/apply-disable-directives.js
@@ -5,8 +5,6 @@
"use strict";
-const lodash = require("lodash");
-
/**
* Compares the locations of two objects in a source file
* @param {{line: number, column: number}} itemA The first object
@@ -124,7 +122,21 @@ module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off"
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
.sort(compareLocations);
- const lineDirectives = lodash.flatMap(directives, directive => {
+ /**
+ * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
+ * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
+ * @param {any[]} array The array to process
+ * @param {Function} fn The function to use
+ * @returns {any[]} The result array
+ */
+ function flatMap(array, fn) {
+ const mapped = array.map(fn);
+ const flattened = [].concat(...mapped);
+
+ return flattened;
+ }
+
+ const lineDirectives = flatMap(directives, directive => {
switch (directive.type) {
case "disable":
case "enable":
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index 576816b5b7b..bdc6c1b1d01 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -15,7 +15,7 @@ const
eslintScope = require("eslint-scope"),
evk = require("eslint-visitor-keys"),
espree = require("espree"),
- lodash = require("lodash"),
+ merge = require("lodash.merge"),
BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
pkg = require("../../package.json"),
astUtils = require("../shared/ast-utils"),
@@ -529,8 +529,8 @@ function normalizeVerifyOptions(providedOptions, config) {
function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
const parserOptionsFromEnv = enabledEnvironments
.filter(env => env.parserOptions)
- .reduce((parserOptions, env) => lodash.merge(parserOptions, env.parserOptions), {});
- const mergedParserOptions = lodash.merge(parserOptionsFromEnv, providedOptions || {});
+ .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
+ const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
const isModule = mergedParserOptions.sourceType === "module";
if (isModule) {
@@ -1286,7 +1286,9 @@ class Linter {
const filenameToExpose = normalizeFilename(filename);
const text = ensureText(textOrSourceCode);
const preprocess = options.preprocess || (rawText => [rawText]);
- const postprocess = options.postprocess || lodash.flatten;
+
+ // TODO(stephenwade): Replace this with array.flat() when we drop support for Node v10
+ const postprocess = options.postprocess || (array => [].concat(...array));
const filterCodeBlock =
options.filterCodeBlock ||
(blockFilename => blockFilename.endsWith(".js"));
diff --git a/lib/linter/node-event-generator.js b/lib/linter/node-event-generator.js
index 0b4e50fc4b7..8b619fdff83 100644
--- a/lib/linter/node-event-generator.js
+++ b/lib/linter/node-event-generator.js
@@ -10,7 +10,6 @@
//------------------------------------------------------------------------------
const esquery = require("esquery");
-const lodash = require("lodash");
//------------------------------------------------------------------------------
// Typedefs
@@ -32,6 +31,35 @@ const lodash = require("lodash");
// Helpers
//------------------------------------------------------------------------------
+/**
+ * Computes the union of one or more arrays
+ * @param {...any[]} arrays One or more arrays to union
+ * @returns {any[]} The union of the input arrays
+ */
+function union(...arrays) {
+
+ // TODO(stephenwade): Replace this with arrays.flat() when we drop support for Node v10
+ return [...new Set([].concat(...arrays))];
+}
+
+/**
+ * Computes the intersection of one or more arrays
+ * @param {...any[]} arrays One or more arrays to intersect
+ * @returns {any[]} The intersection of the input arrays
+ */
+function intersection(...arrays) {
+ if (arrays.length === 0) {
+ return [];
+ }
+
+ let result = [...new Set(arrays[0])];
+
+ for (const array of arrays.slice(1)) {
+ result = result.filter(x => array.includes(x));
+ }
+ return result;
+}
+
/**
* Gets the possible types of a selector
* @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
@@ -46,7 +74,7 @@ function getPossibleTypes(parsedSelector) {
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
if (typesForComponents.every(Boolean)) {
- return lodash.union(...typesForComponents);
+ return union(...typesForComponents);
}
return null;
}
@@ -63,7 +91,7 @@ function getPossibleTypes(parsedSelector) {
* If at least one of the components could only match a particular type, the compound could only match
* the intersection of those types.
*/
- return lodash.intersection(...typesForComponents);
+ return intersection(...typesForComponents);
}
case "child":
@@ -166,15 +194,21 @@ function tryParseSelector(rawSelector) {
}
}
+const selectorCache = new Map();
+
/**
* Parses a raw selector string, and returns the parsed selector along with specificity and type information.
* @param {string} rawSelector A raw AST selector
* @returns {ASTSelector} A selector descriptor
*/
-const parseSelector = lodash.memoize(rawSelector => {
+function parseSelector(rawSelector) {
+ if (selectorCache.has(rawSelector)) {
+ return selectorCache.get(rawSelector);
+ }
+
const parsedSelector = tryParseSelector(rawSelector);
- return {
+ const result = {
rawSelector,
isExit: rawSelector.endsWith(":exit"),
parsedSelector,
@@ -182,7 +216,10 @@ const parseSelector = lodash.memoize(rawSelector => {
attributeCount: countClassAttributes(parsedSelector),
identifierCount: countIdentifiers(parsedSelector)
};
-});
+
+ selectorCache.set(rawSelector, result);
+ return result;
+}
//------------------------------------------------------------------------------
// Public Interface
diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js
index 23b78fba279..b08303c62b2 100644
--- a/lib/rule-tester/rule-tester.js
+++ b/lib/rule-tester/rule-tester.js
@@ -44,7 +44,8 @@ const
assert = require("assert"),
path = require("path"),
util = require("util"),
- lodash = require("lodash"),
+ merge = require("lodash.merge"),
+ equal = require("fast-deep-equal"),
Traverser = require("../../lib/shared/traverser"),
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
@@ -324,10 +325,9 @@ class RuleTester {
* configuration and the default configuration.
* @type {Object}
*/
- this.testerConfig = lodash.merge(
-
- // we have to clone because merge uses the first argument for recipient
- lodash.cloneDeep(defaultConfig),
+ this.testerConfig = merge(
+ {},
+ defaultConfig,
testerConfig,
{ rules: { "rule-tester/validate-ast": "error" } }
);
@@ -369,7 +369,7 @@ class RuleTester {
* @returns {void}
*/
static resetDefaultConfig() {
- defaultConfig = lodash.cloneDeep(testerDefaultConfig);
+ defaultConfig = merge({}, testerDefaultConfig);
}
@@ -465,7 +465,7 @@ class RuleTester {
* @private
*/
function runRuleForItem(item) {
- let config = lodash.cloneDeep(testerConfig),
+ let config = merge({}, testerConfig),
code, filename, output, beforeAST, afterAST;
if (typeof item === "string") {
@@ -477,13 +477,17 @@ class RuleTester {
* Assumes everything on the item is a config except for the
* parameters used by this tester
*/
- const itemConfig = lodash.omit(item, RuleTesterParameters);
+ const itemConfig = { ...item };
+
+ for (const parameter of RuleTesterParameters) {
+ delete itemConfig[parameter];
+ }
/*
* Create the config object from the tester config and this item
* specific configurations.
*/
- config = lodash.merge(
+ config = merge(
config,
itemConfig
);
@@ -589,7 +593,7 @@ class RuleTester {
* @private
*/
function assertASTDidntChange(beforeAST, afterAST) {
- if (!lodash.isEqual(beforeAST, afterAST)) {
+ if (!equal(beforeAST, afterAST)) {
assert.fail("Rule should not modify AST.");
}
}
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index e22b7f3551e..798c111ec3b 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -9,7 +9,6 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -144,23 +143,33 @@ module.exports = {
* @returns {ASTNode|null} The last node or null.
*/
function getLastItem(node) {
+
+ /**
+ * Returns the last element of an array
+ * @param {any[]} array The input array
+ * @returns {any} The last element
+ */
+ function last(array) {
+ return array[array.length - 1];
+ }
+
switch (node.type) {
case "ObjectExpression":
case "ObjectPattern":
- return lodash.last(node.properties);
+ return last(node.properties);
case "ArrayExpression":
case "ArrayPattern":
- return lodash.last(node.elements);
+ return last(node.elements);
case "ImportDeclaration":
case "ExportNamedDeclaration":
- return lodash.last(node.specifiers);
+ return last(node.specifiers);
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
- return lodash.last(node.params);
+ return last(node.params);
case "CallExpression":
case "NewExpression":
- return lodash.last(node.arguments);
+ return last(node.arguments);
default:
return null;
}
@@ -316,7 +325,7 @@ module.exports = {
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma,
- ignore: lodash.noop
+ ignore: () => {}
};
return {
diff --git a/lib/rules/complexity.js b/lib/rules/complexity.js
index 5d62c6ff44b..116c8ad0a63 100644
--- a/lib/rules/complexity.js
+++ b/lib/rules/complexity.js
@@ -10,9 +10,8 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
-
const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
//------------------------------------------------------------------------------
// Rule Definition
@@ -95,7 +94,7 @@ module.exports = {
* @private
*/
function endFunction(node) {
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
const complexity = fns.pop();
if (complexity > THRESHOLD) {
diff --git a/lib/rules/consistent-return.js b/lib/rules/consistent-return.js
index 94db253d25b..a250430cb76 100644
--- a/lib/rules/consistent-return.js
+++ b/lib/rules/consistent-return.js
@@ -8,8 +8,8 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
//------------------------------------------------------------------------------
// Helpers
@@ -164,7 +164,7 @@ module.exports = {
funcInfo.data = {
name: funcInfo.node.type === "Program"
? "Program"
- : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+ : upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
};
} else if (funcInfo.hasReturnValue !== hasReturnValue) {
context.report({
diff --git a/lib/rules/eol-last.js b/lib/rules/eol-last.js
index 89c76acb202..24b0c9279c7 100644
--- a/lib/rules/eol-last.js
+++ b/lib/rules/eol-last.js
@@ -4,12 +4,6 @@
*/
"use strict";
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const lodash = require("lodash");
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -48,8 +42,9 @@ module.exports = {
Program: function checkBadEOF(node) {
const sourceCode = context.getSourceCode(),
src = sourceCode.getText(),
+ lastLine = sourceCode.lines[sourceCode.lines.length - 1],
location = {
- column: lodash.last(sourceCode.lines).length,
+ column: lastLine.length,
line: sourceCode.lines.length
},
LF = "\n",
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 8f4079d31f9..b1af2a73b33 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -12,10 +12,10 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
-const astUtils = require("./utils/ast-utils");
const createTree = require("functional-red-black-tree");
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -1068,7 +1068,7 @@ module.exports = {
const baseOffsetListeners = {
"ArrayExpression, ArrayPattern"(node) {
const openingBracket = sourceCode.getFirstToken(node);
- const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
+ const closingBracket = sourceCode.getTokenAfter([...node.elements].reverse().find(_ => _) || openingBracket, astUtils.isClosingBracketToken);
addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
},
@@ -1560,8 +1560,9 @@ module.exports = {
* 2. Don't set any offsets against the first token of the node.
* 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
*/
- const offsetListeners = lodash.mapValues(
- baseOffsetListeners,
+ const offsetListeners = {};
+
+ for (const [selector, listener] of Object.entries(baseOffsetListeners)) {
/*
* Offset listener calls are deferred until traversal is finished, and are called as
@@ -1579,10 +1580,8 @@ module.exports = {
* To avoid this, the `Identifier` listener isn't called until traversal finishes and all
* ignored nodes are known.
*/
- listener =>
- node =>
- listenerCallQueue.push({ listener, node })
- );
+ offsetListeners[selector] = node => listenerCallQueue.push({ listener, node });
+ }
// For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
const ignoredNodes = new Set();
diff --git a/lib/rules/max-lines-per-function.js b/lib/rules/max-lines-per-function.js
index de70cecb47a..60e2e879f54 100644
--- a/lib/rules/max-lines-per-function.js
+++ b/lib/rules/max-lines-per-function.js
@@ -9,8 +9,7 @@
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
-
-const lodash = require("lodash");
+const { upperCaseFirst } = require("../shared/string-utils");
//------------------------------------------------------------------------------
// Constants
@@ -191,7 +190,7 @@ module.exports = {
}
if (lineCount > maxLines) {
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(funcNode));
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
context.report({
node,
diff --git a/lib/rules/max-lines.js b/lib/rules/max-lines.js
index ceb014aff71..8bd5a1c95f4 100644
--- a/lib/rules/max-lines.js
+++ b/lib/rules/max-lines.js
@@ -8,9 +8,22 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
const astUtils = require("./utils/ast-utils");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Creates an array of numbers from `start` up to, but not including, `end`
+ * @param {number} start The start of the range
+ * @param {number} end The end of the range
+ * @returns {number[]} The range of numbers
+ */
+function range(start, end) {
+ return [...Array(end - start).keys()].map(x => x + start);
+}
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -119,11 +132,25 @@ module.exports = {
}
if (start <= end) {
- return lodash.range(start, end + 1);
+ return range(start, end + 1);
}
return [];
}
+ /**
+ * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
+ * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
+ * @param {any[]} array The array to process
+ * @param {Function} fn The function to use
+ * @returns {any[]} The result array
+ */
+ function flatMap(array, fn) {
+ const mapped = array.map(fn);
+ const flattened = [].concat(...mapped);
+
+ return flattened;
+ }
+
return {
"Program:exit"() {
let lines = sourceCode.lines.map((text, i) => ({
@@ -135,7 +162,7 @@ module.exports = {
* If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
* That isn't a real line, so we shouldn't count it.
*/
- if (lines.length > 1 && lodash.last(lines).text === "") {
+ if (lines.length > 1 && lines[lines.length - 1].text === "") {
lines.pop();
}
@@ -146,9 +173,7 @@ module.exports = {
if (skipComments) {
const comments = sourceCode.getAllComments();
- const commentLines = lodash.flatten(
- comments.map(comment => getLinesWithoutCode(comment))
- );
+ const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
lines = lines.filter(
l => !commentLines.includes(l.lineNumber)
@@ -163,7 +188,7 @@ module.exports = {
},
end: {
line: sourceCode.lines.length,
- column: lodash.last(sourceCode.lines).length
+ column: sourceCode.lines[sourceCode.lines.length - 1].length
}
};
diff --git a/lib/rules/max-params.js b/lib/rules/max-params.js
index 4eebe2d95a3..8fb798401cb 100644
--- a/lib/rules/max-params.js
+++ b/lib/rules/max-params.js
@@ -9,9 +9,8 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
-
const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
//------------------------------------------------------------------------------
// Rule Definition
@@ -85,7 +84,7 @@ module.exports = {
node,
messageId: "exceed",
data: {
- name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)),
+ name: upperCaseFirst(astUtils.getFunctionNameWithKind(node)),
count: node.params.length,
max: numParams
}
diff --git a/lib/rules/max-statements.js b/lib/rules/max-statements.js
index 437b393a508..65d5539550d 100644
--- a/lib/rules/max-statements.js
+++ b/lib/rules/max-statements.js
@@ -9,9 +9,8 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
-
const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
//------------------------------------------------------------------------------
// Rule Definition
@@ -97,7 +96,7 @@ module.exports = {
*/
function reportIfTooManyStatements(node, count, max) {
if (count > max) {
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
context.report({
node,
diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js
index dd1f3ed9d9a..e8016e93e59 100644
--- a/lib/rules/no-fallthrough.js
+++ b/lib/rules/no-fallthrough.js
@@ -4,12 +4,6 @@
*/
"use strict";
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const lodash = require("lodash");
-
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@@ -25,7 +19,7 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
*/
function hasFallthroughComment(node, context, fallthroughCommentPattern) {
const sourceCode = context.getSourceCode();
- const comment = lodash.last(sourceCode.getCommentsBefore(node));
+ const comment = sourceCode.getCommentsBefore(node).pop();
return Boolean(comment && fallthroughCommentPattern.test(comment.value));
}
@@ -133,7 +127,7 @@ module.exports = {
*/
if (currentCodePath.currentSegments.some(isReachable) &&
(node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) &&
- lodash.last(node.parent.cases) !== node) {
+ node.parent.cases[node.parent.cases.length - 1] !== node) {
fallthroughCase = node;
}
}
diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js
index 958e3d5dd4e..529c16439e3 100644
--- a/lib/rules/no-useless-backreference.js
+++ b/lib/rules/no-useless-backreference.js
@@ -11,7 +11,6 @@
const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils");
const { RegExpParser, visitRegExpAST } = require("regexpp");
-const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@@ -137,7 +136,7 @@ module.exports = {
// the opposite of the previous when the regex is matching backward in a lookbehind context.
messageId = "backward";
- } else if (lodash.last(groupCut).type === "Alternative") {
+ } else if (groupCut[groupCut.length - 1].type === "Alternative") {
// group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive.
messageId = "disjunctive";
diff --git a/lib/rules/no-useless-computed-key.js b/lib/rules/no-useless-computed-key.js
index e0505a318ef..a1cacc29612 100644
--- a/lib/rules/no-useless-computed-key.js
+++ b/lib/rules/no-useless-computed-key.js
@@ -8,7 +8,6 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -95,9 +94,16 @@ module.exports = {
}
}
+ /**
+ * A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`.
+ * @returns {void}
+ * @private
+ */
+ function noop() {}
+
return {
Property: check,
- MethodDefinition: enforceForClassMembers ? check : lodash.noop
+ MethodDefinition: enforceForClassMembers ? check : noop
};
}
};
diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js
index 0691a31f77e..e5f702bc7d7 100644
--- a/lib/rules/no-warning-comments.js
+++ b/lib/rules/no-warning-comments.js
@@ -5,7 +5,7 @@
"use strict";
-const { escapeRegExp } = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
const astUtils = require("./utils/ast-utils");
const CHAR_LIMIT = 40;
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index 9b64a1b5c6a..1fbea00c5d7 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -10,7 +10,6 @@
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
-const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@@ -69,6 +68,24 @@ function normalizeOptionValue(value) {
return { multiline, minProperties, consistent };
}
+/**
+ * Checks if a value is an object.
+ * @param {any} value The value to check
+ * @returns {boolean} `true` if the value is an object, otherwise `false`
+ */
+function isObject(value) {
+ return typeof value === "object" && value !== null;
+}
+
+/**
+ * Checks if an option is a node-specific option
+ * @param {any} option The option to check
+ * @returns {boolean} `true` if the option is node-specific, otherwise `false`
+ */
+function isNodeSpecificOption(option) {
+ return isObject(option) || typeof option === "string";
+}
+
/**
* Normalizes a given option value.
* @param {string|Object|undefined} options An option value to parse.
@@ -80,9 +97,7 @@ function normalizeOptionValue(value) {
* }} Normalized option object.
*/
function normalizeOptions(options) {
- const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]);
-
- if (lodash.isPlainObject(options) && Object.values(options).some(isNodeSpecificOption)) {
+ if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) {
return {
ObjectExpression: normalizeOptionValue(options.ObjectExpression),
ObjectPattern: normalizeOptionValue(options.ObjectPattern),
diff --git a/lib/rules/spaced-comment.js b/lib/rules/spaced-comment.js
index d3221f0ea79..226a2d44798 100644
--- a/lib/rules/spaced-comment.js
+++ b/lib/rules/spaced-comment.js
@@ -4,7 +4,7 @@
*/
"use strict";
-const lodash = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -17,7 +17,7 @@ const astUtils = require("./utils/ast-utils");
* @returns {string} An escaped string.
*/
function escape(s) {
- return `(?:${lodash.escapeRegExp(s)})`;
+ return `(?:${escapeRegExp(s)})`;
}
/**
diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js
index 679eebb4c45..6b853001132 100644
--- a/lib/rules/utils/ast-utils.js
+++ b/lib/rules/utils/ast-utils.js
@@ -11,7 +11,7 @@
const esutils = require("esutils");
const espree = require("espree");
-const lodash = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
const {
breakableTypePattern,
createGlobalLinebreakMatcher,
@@ -1756,7 +1756,7 @@ module.exports = {
* @returns {SourceLocation} The `loc` object.
*/
getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
- const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
+ const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
// To ignore the first text "global".
namePattern.lastIndex = comment.value.indexOf("global") + 6;
diff --git a/lib/shared/deprecation-warnings.js b/lib/shared/deprecation-warnings.js
index 1438eaa69bf..1a0501ab057 100644
--- a/lib/shared/deprecation-warnings.js
+++ b/lib/shared/deprecation-warnings.js
@@ -9,7 +9,6 @@
//------------------------------------------------------------------------------
const path = require("path");
-const lodash = require("lodash");
//------------------------------------------------------------------------------
// Private
@@ -28,6 +27,8 @@ const deprecationWarningMessages = {
"projects in order to avoid loading '~/.eslintrc.*' accidentally."
};
+const sourceFileErrorCache = new Set();
+
/**
* Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
* for each unique file path, but repeated invocations with the same file path have no effect.
@@ -36,7 +37,15 @@ const deprecationWarningMessages = {
* @param {string} errorCode The warning message to show.
* @returns {void}
*/
-const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
+function emitDeprecationWarning(source, errorCode) {
+ const cacheKey = JSON.stringify({ source, errorCode });
+
+ if (sourceFileErrorCache.has(cacheKey)) {
+ return;
+ }
+
+ sourceFileErrorCache.add(cacheKey);
+
const rel = path.relative(process.cwd(), source);
const message = deprecationWarningMessages[errorCode];
@@ -45,7 +54,7 @@ const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
"DeprecationWarning",
errorCode
);
-}, (...args) => JSON.stringify(args));
+}
//------------------------------------------------------------------------------
// Public Interface
diff --git a/lib/shared/string-utils.js b/lib/shared/string-utils.js
new file mode 100644
index 00000000000..e4a55d79931
--- /dev/null
+++ b/lib/shared/string-utils.js
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Utilities to operate on strings.
+ * @author Stephen Wade
+ */
+
+"use strict";
+
+/**
+ * Converts the first letter of a string to uppercase.
+ * @param {string} string The string to operate on
+ * @returns {string} The converted string
+ */
+function upperCaseFirst(string) {
+ if (string.length <= 1) {
+ return string.toUpperCase();
+ }
+ return string[0].toUpperCase() + string.slice(1);
+}
+
+module.exports = {
+ upperCaseFirst
+};
diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js
index 6b20495b6fc..c13ce29b877 100644
--- a/lib/source-code/source-code.js
+++ b/lib/source-code/source-code.js
@@ -12,8 +12,7 @@ const
{ isCommentToken } = require("eslint-utils"),
TokenStore = require("./token-store"),
astUtils = require("../shared/ast-utils"),
- Traverser = require("../shared/traverser"),
- lodash = require("lodash");
+ Traverser = require("../shared/traverser");
//------------------------------------------------------------------------------
// Private
@@ -531,10 +530,12 @@ class SourceCode extends TokenStore {
}
/*
- * To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
- * be inserted into lineIndices to keep the list sorted.
+ * To figure out which line index is on, determine the last place at which index could
+ * be inserted into lineStartIndices to keep the list sorted.
*/
- const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index);
+ const lineNumber = index >= this.lineStartIndices[this.lineStartIndices.length - 1]
+ ? this.lineStartIndices.length
+ : this.lineStartIndices.findIndex(el => index < el);
return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] };
}
diff --git a/lib/source-code/token-store/utils.js b/lib/source-code/token-store/utils.js
index 21e1d6ff7c3..a2bd77de71a 100644
--- a/lib/source-code/token-store/utils.js
+++ b/lib/source-code/token-store/utils.js
@@ -4,12 +4,6 @@
*/
"use strict";
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const lodash = require("lodash");
-
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@@ -29,18 +23,16 @@ function getStartLocation(token) {
//------------------------------------------------------------------------------
/**
- * Binary-searches the index of the first token which is after the given location.
+ * Finds the index of the first token which is after the given location.
* If it was not found, this returns `tokens.length`.
* @param {(Token|Comment)[]} tokens It searches the token in this list.
* @param {number} location The location to search.
* @returns {number} The found index or `tokens.length`.
*/
exports.search = function search(tokens, location) {
- return lodash.sortedIndexBy(
- tokens,
- { range: [location] },
- getStartLocation
- );
+ const index = tokens.findIndex(el => location <= getStartLocation(el));
+
+ return index === -1 ? tokens.length : index;
};
/**
diff --git a/messages/all-files-ignored.txt b/messages/all-files-ignored.js
similarity index 51%
rename from messages/all-files-ignored.txt
rename to messages/all-files-ignored.js
index 3f4c8ced4c9..d85828d36e7 100644
--- a/messages/all-files-ignored.txt
+++ b/messages/all-files-ignored.js
@@ -1,8 +1,16 @@
-You are linting "<%= pattern %>", but all of the files matching the glob pattern "<%= pattern %>" are ignored.
+"use strict";
-If you don't want to lint these files, remove the pattern "<%= pattern %>" from the list of arguments passed to ESLint.
+module.exports = function(it) {
+ const { pattern } = it;
+
+ return `
+You are linting "${pattern}", but all of the files matching the glob pattern "${pattern}" are ignored.
+
+If you don't want to lint these files, remove the pattern "${pattern}" from the list of arguments passed to ESLint.
If you do want to lint these files, try the following solutions:
* Check your .eslintignore file, or the eslintIgnore property in package.json, to ensure that the files are not configured to be ignored.
* Explicitly list the files from this glob that you'd like to lint on the command-line, rather than providing a glob as an argument.
+`.trimLeft();
+};
diff --git a/messages/extend-config-missing.js b/messages/extend-config-missing.js
new file mode 100644
index 00000000000..db8a5c64b9f
--- /dev/null
+++ b/messages/extend-config-missing.js
@@ -0,0 +1,13 @@
+"use strict";
+
+module.exports = function(it) {
+ const { configName, importerName } = it;
+
+ return `
+ESLint couldn't find the config "${configName}" to extend from. Please check that the name of the config is correct.
+
+The config "${configName}" was referenced from the config file in "${importerName}".
+
+If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/messages/extend-config-missing.txt b/messages/extend-config-missing.txt
deleted file mode 100644
index 4defd7ac4d1..00000000000
--- a/messages/extend-config-missing.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-ESLint couldn't find the config "<%- configName %>" to extend from. Please check that the name of the config is correct.
-
-The config "<%- configName %>" was referenced from the config file in "<%- importerName %>".
-
-If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/messages/failed-to-read-json.js b/messages/failed-to-read-json.js
new file mode 100644
index 00000000000..5114de30980
--- /dev/null
+++ b/messages/failed-to-read-json.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = function(it) {
+ const { path, message } = it;
+
+ return `
+Failed to read JSON file at ${path}:
+
+${message}
+`.trimLeft();
+};
diff --git a/messages/failed-to-read-json.txt b/messages/failed-to-read-json.txt
deleted file mode 100644
index b5e2b861cfc..00000000000
--- a/messages/failed-to-read-json.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Failed to read JSON file at <%= path %>:
-
-<%= message %>
diff --git a/messages/file-not-found.js b/messages/file-not-found.js
new file mode 100644
index 00000000000..26a5d57eff7
--- /dev/null
+++ b/messages/file-not-found.js
@@ -0,0 +1,10 @@
+"use strict";
+
+module.exports = function(it) {
+ const { pattern, globDisabled } = it;
+
+ return `
+No files matching the pattern "${pattern}"${globDisabled ? " (with disabling globs)" : ""} were found.
+Please check for typing mistakes in the pattern.
+`.trimLeft();
+};
diff --git a/messages/file-not-found.txt b/messages/file-not-found.txt
deleted file mode 100644
index 639498eb5c6..00000000000
--- a/messages/file-not-found.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-No files matching the pattern "<%= pattern %>"<% if (globDisabled) { %> (with disabling globs)<% } %> were found.
-Please check for typing mistakes in the pattern.
diff --git a/messages/no-config-found.txt b/messages/no-config-found.js
similarity index 52%
rename from messages/no-config-found.txt
rename to messages/no-config-found.js
index b46a7e5a7a6..1042143f9fd 100644
--- a/messages/no-config-found.txt
+++ b/messages/no-config-found.js
@@ -1,7 +1,15 @@
+"use strict";
+
+module.exports = function(it) {
+ const { directoryPath } = it;
+
+ return `
ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:
eslint --init
-ESLint looked for configuration files in <%= directoryPath %> and its ancestors. If it found none, it then looked in your home directory.
+ESLint looked for configuration files in ${directoryPath} and its ancestors. If it found none, it then looked in your home directory.
If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat/help
+`.trimLeft();
+};
diff --git a/messages/plugin-conflict.js b/messages/plugin-conflict.js
new file mode 100644
index 00000000000..c8c060e2f05
--- /dev/null
+++ b/messages/plugin-conflict.js
@@ -0,0 +1,22 @@
+"use strict";
+
+module.exports = function(it) {
+ const { pluginId, plugins } = it;
+
+ let result = `ESLint couldn't determine the plugin "${pluginId}" uniquely.
+`;
+
+ for (const { filePath, importerName } of plugins) {
+ result += `
+- ${filePath} (loaded in "${importerName}")`;
+ }
+
+ result += `
+
+Please remove the "plugins" setting from either config or remove either plugin installation.
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`;
+
+ return result;
+};
diff --git a/messages/plugin-conflict.txt b/messages/plugin-conflict.txt
deleted file mode 100644
index 3ab4b340ef2..00000000000
--- a/messages/plugin-conflict.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-ESLint couldn't determine the plugin "<%- pluginId %>" uniquely.
-<% for (const { filePath, importerName } of plugins) { %>
-- <%= filePath %> (loaded in "<%= importerName %>")<% } %>
-
-Please remove the "plugins" setting from either config or remove either plugin installation.
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/messages/plugin-invalid.js b/messages/plugin-invalid.js
new file mode 100644
index 00000000000..7913576f000
--- /dev/null
+++ b/messages/plugin-invalid.js
@@ -0,0 +1,16 @@
+"use strict";
+
+module.exports = function(it) {
+ const { configName, importerName } = it;
+
+ return `
+"${configName}" is invalid syntax for a config specifier.
+
+* If your intention is to extend from a configuration exported from the plugin, add the configuration name after a slash: e.g. "${configName}/myConfig".
+* If this is the name of a shareable config instead of a plugin, remove the "plugin:" prefix: i.e. "${configName.slice("plugin:".length)}".
+
+"${configName}" was referenced from the config file in "${importerName}".
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/messages/plugin-invalid.txt b/messages/plugin-invalid.txt
deleted file mode 100644
index 3ee251821be..00000000000
--- a/messages/plugin-invalid.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-"<%- configName %>" is invalid syntax for a config specifier.
-
-* If your intention is to extend from a configuration exported from the plugin, add the configuration name after a slash: e.g. "<%- configName %>/myConfig".
-* If this is the name of a shareable config instead of a plugin, remove the "plugin:" prefix: i.e. "<%- configName.slice("plugin:".length) %>".
-
-"<%- configName %>" was referenced from the config file in "<%- importerName %>".
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/messages/plugin-missing.js b/messages/plugin-missing.js
new file mode 100644
index 00000000000..f58c78ceb38
--- /dev/null
+++ b/messages/plugin-missing.js
@@ -0,0 +1,19 @@
+"use strict";
+
+module.exports = function(it) {
+ const { pluginName, resolvePluginsRelativeTo, importerName } = it;
+
+ return `
+ESLint couldn't find the plugin "${pluginName}".
+
+(The package "${pluginName}" was not found when loaded as a Node module from the directory "${resolvePluginsRelativeTo}".)
+
+It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
+
+ npm install ${pluginName}@latest --save-dev
+
+The plugin "${pluginName}" was referenced from the config file in "${importerName}".
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/messages/plugin-missing.txt b/messages/plugin-missing.txt
deleted file mode 100644
index aa25f59ac44..00000000000
--- a/messages/plugin-missing.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-ESLint couldn't find the plugin "<%- pluginName %>".
-
-(The package "<%- pluginName %>" was not found when loaded as a Node module from the directory "<%- resolvePluginsRelativeTo %>".)
-
-It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
-
- npm install <%- pluginName %>@latest --save-dev
-
-The plugin "<%- pluginName %>" was referenced from the config file in "<%- importerName %>".
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/messages/print-config-with-directory-path.txt b/messages/print-config-with-directory-path.js
similarity index 70%
rename from messages/print-config-with-directory-path.txt
rename to messages/print-config-with-directory-path.js
index 1afc9b1e88b..6a5d571dd37 100644
--- a/messages/print-config-with-directory-path.txt
+++ b/messages/print-config-with-directory-path.js
@@ -1,2 +1,8 @@
+"use strict";
+
+module.exports = function() {
+ return `
The '--print-config' CLI option requires a path to a source code file rather than a directory.
See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config
+`.trimLeft();
+};
diff --git a/messages/whitespace-found.js b/messages/whitespace-found.js
new file mode 100644
index 00000000000..4ce49ca3a4e
--- /dev/null
+++ b/messages/whitespace-found.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = function(it) {
+ const { pluginName } = it;
+
+ return `
+ESLint couldn't find the plugin "${pluginName}". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name.
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/messages/whitespace-found.txt b/messages/whitespace-found.txt
deleted file mode 100644
index 3eed1af5866..00000000000
--- a/messages/whitespace-found.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-ESLint couldn't find the plugin "<%- pluginName %>". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name.
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/package.json b/package.json
index 51a76ec31c8..31d96fe28cf 100644
--- a/package.json
+++ b/package.json
@@ -51,12 +51,14 @@
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.1",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^5.0.0",
@@ -68,7 +70,7 @@
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
- "lodash": "^4.17.21",
+ "lodash.merge": "^4.6.2",
"minimatch": "^3.0.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
@@ -77,7 +79,7 @@
"semver": "^7.2.1",
"strip-ansi": "^6.0.0",
"strip-json-comments": "^3.1.0",
- "table": "^6.0.4",
+ "table": "^6.0.9",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
@@ -91,7 +93,6 @@
"core-js": "^3.1.3",
"dateformat": "^3.0.3",
"ejs": "^3.0.2",
- "escape-string-regexp": "^3.0.0",
"eslint": "file:.",
"eslint-config-eslint": "file:packages/eslint-config-eslint",
"eslint-plugin-eslint-plugin": "^2.2.1",
diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js
index 3f2393621b6..3aaf6f91f3f 100644
--- a/tests/lib/rule-tester/rule-tester.js
+++ b/tests/lib/rule-tester/rule-tester.js
@@ -11,8 +11,7 @@ const sinon = require("sinon"),
EventEmitter = require("events"),
{ RuleTester } = require("../../../lib/rule-tester"),
assert = require("chai").assert,
- nodeAssert = require("assert"),
- { noop } = require("lodash");
+ nodeAssert = require("assert");
const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
try {
@@ -23,6 +22,15 @@ const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
throw new Error("unexpected successful assertion");
})();
+/**
+ * Do nothing.
+ * @returns {void}
+ */
+function noop() {
+
+ // do nothing.
+}
+
//------------------------------------------------------------------------------
// Rewire Things
//------------------------------------------------------------------------------
diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js
index 6e1b757a712..3b19d63696f 100644
--- a/tests/lib/rules/no-invalid-this.js
+++ b/tests/lib/rules/no-invalid-this.js
@@ -9,7 +9,8 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
+const merge = require("lodash.merge");
+
const rule = require("../../../lib/rules/no-invalid-this");
const { RuleTester } = require("../../../lib/rule-tester");
@@ -69,7 +70,7 @@ function extractPatterns(patterns, type) {
// Clone and apply the pattern environment.
const patternsList = patterns.map(pattern => pattern[type].map(applyCondition => {
- const thisPattern = lodash.cloneDeep(pattern);
+ const thisPattern = merge({}, pattern);
applyCondition(thisPattern);
@@ -79,7 +80,10 @@ function extractPatterns(patterns, type) {
thisPattern.code += " /* should error */";
}
- return lodash.omit(thisPattern, ["valid", "invalid"]);
+ delete thisPattern.valid;
+ delete thisPattern.invalid;
+
+ return thisPattern;
}));
// Flatten.
diff --git a/tests/lib/shared/string-utils.js b/tests/lib/shared/string-utils.js
new file mode 100644
index 00000000000..bc48afaab4e
--- /dev/null
+++ b/tests/lib/shared/string-utils.js
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview Tests for string utils.
+ * @author Stephen Wade
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("chai").assert;
+
+const { upperCaseFirst } = require("../../../lib/shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+describe("upperCaseFirst", () => {
+ it("uppercases the first letter of a string", () => {
+ assert(upperCaseFirst("e") === "E");
+ assert(upperCaseFirst("alphabet") === "Alphabet");
+ assert(upperCaseFirst("one two three") === "One two three");
+ });
+
+ it("only changes the case of the first letter", () => {
+ assert(upperCaseFirst("alphaBet") === "AlphaBet");
+ assert(upperCaseFirst("one TWO three") === "One TWO three");
+ });
+
+ it("does not change the case if the first letter is already uppercase", () => {
+ assert(upperCaseFirst("E") === "E");
+ assert(upperCaseFirst("Alphabet") === "Alphabet");
+ assert(upperCaseFirst("One Two Three") === "One Two Three");
+ });
+
+ it("properly handles an empty string", () => {
+ assert(upperCaseFirst("") === "");
+ });
+});
diff --git a/tools/eslint-fuzzer.js b/tools/eslint-fuzzer.js
index 4104b8516b2..c9705200eaf 100644
--- a/tools/eslint-fuzzer.js
+++ b/tools/eslint-fuzzer.js
@@ -10,13 +10,25 @@
//------------------------------------------------------------------------------
const assert = require("assert");
-const lodash = require("lodash");
const eslump = require("eslump");
const espree = require("espree");
const SourceCodeFixer = require("../lib/linter/source-code-fixer");
const ruleConfigs = require("../lib/init/config-rule").createCoreRuleConfigs(true);
const sampleMinimizer = require("./code-sample-minimizer");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Gets a random item from an array
+ * @param {any[]} array The array to sample
+ * @returns {any} The random item
+ */
+function sample(array) {
+ return array[Math.floor(Math.random() * array.length)];
+}
+
//------------------------------------------------------------------------------
// Public API
//------------------------------------------------------------------------------
@@ -125,10 +137,16 @@ function fuzz(options) {
}
for (let i = 0; i < options.count; progressCallback(problems.length), i++) {
- const sourceType = lodash.sample(["script", "module"]);
+ const rules = {};
+
+ for (const [id, configs] of Object.entries(ruleConfigs)) {
+ rules[id] = sample(configs);
+ }
+
+ const sourceType = sample(["script", "module"]);
const text = codeGenerator({ sourceType });
const config = {
- rules: lodash.mapValues(ruleConfigs, lodash.sample),
+ rules,
parserOptions: {
sourceType,
ecmaVersion: espree.latestEcmaVersion
diff --git a/webpack.config.js b/webpack.config.js
index b256f442460..a22c99bf861 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -39,8 +39,7 @@ module.exports = {
targets: ">0.5%, not chrome 49, not ie 11, not safari 5.1"
}]
]
- },
- exclude: /node_modules[\\/]lodash/u
+ }
}
]
},