summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/eslint/lib')
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js51
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/cli-engine.js54
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/config-array-factory.js114
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js13
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/config-array/extracted-config.js27
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/config-array/ignore-pattern.js231
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/config-array/index.js2
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/file-enumerator.js73
-rw-r--r--tools/node_modules/eslint/lib/cli-engine/ignored-paths.js363
-rw-r--r--tools/node_modules/eslint/lib/init/config-initializer.js7
-rw-r--r--tools/node_modules/eslint/lib/linter/report-translator.js80
-rw-r--r--tools/node_modules/eslint/lib/rule-tester/rule-tester.js46
-rw-r--r--tools/node_modules/eslint/lib/rules/camelcase.js25
-rw-r--r--tools/node_modules/eslint/lib/rules/comma-dangle.js7
-rw-r--r--tools/node_modules/eslint/lib/rules/computed-property-spacing.js8
-rw-r--r--tools/node_modules/eslint/lib/rules/curly.js13
-rw-r--r--tools/node_modules/eslint/lib/rules/function-call-argument-newline.js4
-rw-r--r--tools/node_modules/eslint/lib/rules/grouped-accessor-pairs.js224
-rw-r--r--tools/node_modules/eslint/lib/rules/indent.js11
-rw-r--r--tools/node_modules/eslint/lib/rules/index.js5
-rw-r--r--tools/node_modules/eslint/lib/rules/multiline-comment-style.js343
-rw-r--r--tools/node_modules/eslint/lib/rules/no-cond-assign.js18
-rw-r--r--tools/node_modules/eslint/lib/rules/no-constructor-return.js62
-rw-r--r--tools/node_modules/eslint/lib/rules/no-dupe-else-if.js122
-rw-r--r--tools/node_modules/eslint/lib/rules/no-implicit-globals.js98
-rw-r--r--tools/node_modules/eslint/lib/rules/no-inline-comments.js36
-rw-r--r--tools/node_modules/eslint/lib/rules/no-invalid-this.js18
-rw-r--r--tools/node_modules/eslint/lib/rules/no-octal-escape.js2
-rw-r--r--tools/node_modules/eslint/lib/rules/no-setter-return.js227
-rw-r--r--tools/node_modules/eslint/lib/rules/no-underscore-dangle.js27
-rw-r--r--tools/node_modules/eslint/lib/rules/no-useless-computed-key.js93
-rw-r--r--tools/node_modules/eslint/lib/rules/no-useless-escape.js29
-rw-r--r--tools/node_modules/eslint/lib/rules/object-curly-spacing.js16
-rw-r--r--tools/node_modules/eslint/lib/rules/operator-assignment.js13
-rw-r--r--tools/node_modules/eslint/lib/rules/prefer-const.js21
-rw-r--r--tools/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js189
-rw-r--r--tools/node_modules/eslint/lib/rules/require-await.js8
-rw-r--r--tools/node_modules/eslint/lib/rules/semi.js9
-rw-r--r--tools/node_modules/eslint/lib/rules/space-infix-ops.js2
-rw-r--r--tools/node_modules/eslint/lib/rules/spaced-comment.js9
-rw-r--r--tools/node_modules/eslint/lib/rules/utils/ast-utils.js35
-rw-r--r--tools/node_modules/eslint/lib/shared/types.js9
-rw-r--r--tools/node_modules/eslint/lib/source-code/source-code.js65
43 files changed, 2147 insertions, 662 deletions
diff --git a/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js b/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js
index 6c914ea491..52703a873b 100644
--- a/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js
+++ b/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js
@@ -27,7 +27,7 @@ const os = require("os");
const path = require("path");
const { validateConfigArray } = require("../shared/config-validator");
const { ConfigArrayFactory } = require("./config-array-factory");
-const { ConfigArray, ConfigDependency } = require("./config-array");
+const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array");
const loadRules = require("./load-rules");
const debug = require("debug")("eslint:cascading-config-array-factory");
@@ -45,8 +45,9 @@ const debug = require("debug")("eslint:cascading-config-array-factory");
* @typedef {Object} CascadingConfigArrayFactoryOptions
* @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
* @property {ConfigData} [baseConfig] The config by `baseConfig` option.
- * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files.
+ * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--ignore-pattern`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files.
* @property {string} [cwd] The base directory to start lookup.
+ * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
* @property {string[]} [rulePaths] The value of `--rulesdir` option.
* @property {string} [specificConfigPath] The value of `--config` option.
* @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
@@ -62,6 +63,7 @@ const debug = require("debug")("eslint:cascading-config-array-factory");
* @property {Map<string, ConfigArray>} configCache The cache from directory paths to config arrays.
* @property {string} cwd The base directory to start lookup.
* @property {WeakMap<ConfigArray, ConfigArray>} finalizeCache The cache from config arrays to finalized config arrays.
+ * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
* @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
* @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
* @property {boolean} useEslintrc if `false` then it doesn't load config files.
@@ -86,14 +88,22 @@ function createBaseConfigArray({
{ name: "BaseConfig" }
);
+ /*
+ * Create the config array element for the default ignore patterns.
+ * This element has `ignorePattern` property that ignores the default
+ * patterns in the current working directory.
+ */
+ baseConfigArray.unshift(configArrayFactory.create(
+ { ignorePatterns: IgnorePattern.DefaultPatterns },
+ { name: "DefaultIgnorePattern" }
+ )[0]);
+
+ /*
+ * Load rules `--rulesdir` option as a pseudo plugin.
+ * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
+ * the rule's options with only information in the config array.
+ */
if (rulePaths && rulePaths.length > 0) {
-
- /*
- * Load rules `--rulesdir` option as a pseudo plugin.
- * Use a pseudo plugin to define rules of `--rulesdir`, so we can
- * validate the rule's options with only information in the config
- * array.
- */
baseConfigArray.push({
name: "--rulesdir",
filePath: "",
@@ -128,6 +138,7 @@ function createBaseConfigArray({
function createCLIConfigArray({
cliConfigData,
configArrayFactory,
+ ignorePath,
specificConfigPath
}) {
const cliConfigArray = configArrayFactory.create(
@@ -135,6 +146,12 @@ function createCLIConfigArray({
{ name: "CLIOptions" }
);
+ cliConfigArray.unshift(
+ ...(ignorePath
+ ? configArrayFactory.loadESLintIgnore(ignorePath)
+ : configArrayFactory.loadDefaultESLintIgnore())
+ );
+
if (specificConfigPath) {
cliConfigArray.unshift(
...configArrayFactory.loadFile(
@@ -178,6 +195,7 @@ class CascadingConfigArrayFactory {
baseConfig: baseConfigData = null,
cliConfig: cliConfigData = null,
cwd = process.cwd(),
+ ignorePath,
resolvePluginsRelativeTo = cwd,
rulePaths = [],
specificConfigPath = null,
@@ -200,6 +218,7 @@ class CascadingConfigArrayFactory {
cliConfigArray: createCLIConfigArray({
cliConfigData,
configArrayFactory,
+ ignorePath,
specificConfigPath
}),
cliConfigData,
@@ -207,6 +226,7 @@ class CascadingConfigArrayFactory {
configCache: new Map(),
cwd,
finalizeCache: new WeakMap(),
+ ignorePath,
rulePaths,
specificConfigPath,
useEslintrc
@@ -229,9 +249,11 @@ class CascadingConfigArrayFactory {
* If `filePath` was not given, it returns the config which contains only
* `baseConfigData` and `cliConfigData`.
* @param {string} [filePath] The file path to a file.
+ * @param {Object} [options] The options.
+ * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
* @returns {ConfigArray} The config array of the file.
*/
- getConfigArrayForFile(filePath) {
+ getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
const {
baseConfigArray,
cliConfigArray,
@@ -248,7 +270,8 @@ class CascadingConfigArrayFactory {
return this._finalizeConfigArray(
this._loadConfigInAncestors(directoryPath),
- directoryPath
+ directoryPath,
+ ignoreNotFoundError
);
}
@@ -354,10 +377,11 @@ class CascadingConfigArrayFactory {
* Concatenate `--config` and other CLI options.
* @param {ConfigArray} configArray The parent config array.
* @param {string} directoryPath The path to the leaf directory to find config files.
+ * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
* @returns {ConfigArray} The loaded config.
* @private
*/
- _finalizeConfigArray(configArray, directoryPath) {
+ _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
const {
cliConfigArray,
configArrayFactory,
@@ -403,7 +427,8 @@ class CascadingConfigArrayFactory {
);
}
- if (useEslintrc && finalConfigArray.length === 0) {
+ // At least one element (the default ignore patterns) exists.
+ if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
throw new ConfigurationNotFoundError(directoryPath);
}
diff --git a/tools/node_modules/eslint/lib/cli-engine/cli-engine.js b/tools/node_modules/eslint/lib/cli-engine/cli-engine.js
index 8afd262708..0b1c76bac6 100644
--- a/tools/node_modules/eslint/lib/cli-engine/cli-engine.js
+++ b/tools/node_modules/eslint/lib/cli-engine/cli-engine.js
@@ -25,10 +25,9 @@ const ModuleResolver = require("../shared/relative-module-resolver");
const { Linter } = require("../linter");
const builtInRules = require("../rules");
const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
-const { getUsedExtractedConfigs } = require("./config-array");
+const { IgnorePattern, getUsedExtractedConfigs } = require("./config-array");
const { FileEnumerator } = require("./file-enumerator");
const hash = require("./hash");
-const { IgnoredPaths } = require("./ignored-paths");
const LintResultCache = require("./lint-result-cache");
const debug = require("debug")("eslint:cli-engine");
@@ -64,7 +63,7 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @property {string[]} globals An array of global variables to declare.
* @property {boolean} ignore False disables use of .eslintignore.
* @property {string} ignorePath The ignore file to use instead of .eslintignore.
- * @property {string} ignorePattern A glob pattern of files to ignore.
+ * @property {string|string[]} ignorePattern One or more glob patterns to ignore.
* @property {boolean} useEslintrc False disables looking for .eslintrc
* @property {string} parser The name of the parser to use.
* @property {ParserOptions} parserOptions An object of parserOption settings to use.
@@ -113,8 +112,8 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @property {Map<string, Plugin>} additionalPluginPool The map for additional plugins.
* @property {string} cacheFilePath The path to the cache of lint results.
* @property {CascadingConfigArrayFactory} configArrayFactory The factory of configs.
+ * @property {(filePath: string) => boolean} defaultIgnores The default predicate function to check if a file ignored or not.
* @property {FileEnumerator} fileEnumerator The file enumerator.
- * @property {IgnoredPaths} ignoredPaths The ignored paths.
* @property {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
* @property {LintResultCache|null} lintResultCache The cache of lint results.
* @property {Linter} linter The linter instance which has loaded rules.
@@ -486,13 +485,20 @@ function toBooleanMap(keys, defaultValue, displayName) {
* @returns {ConfigData|null} The created config data.
*/
function createConfigDataFromOptions(options) {
- const { parser, parserOptions, plugins, rules } = options;
+ const {
+ ignorePattern,
+ parser,
+ parserOptions,
+ plugins,
+ rules
+ } = options;
const env = toBooleanMap(options.envs, true, "envs");
const globals = toBooleanMap(options.globals, false, "globals");
if (
env === void 0 &&
globals === void 0 &&
+ (ignorePattern === void 0 || ignorePattern.length === 0) &&
parser === void 0 &&
parserOptions === void 0 &&
plugins === void 0 &&
@@ -500,7 +506,15 @@ function createConfigDataFromOptions(options) {
) {
return null;
}
- return { env, globals, parser, parserOptions, plugins, rules };
+ return {
+ env,
+ globals,
+ ignorePatterns: ignorePattern,
+ parser,
+ parserOptions,
+ plugins,
+ rules
+ };
}
/**
@@ -551,19 +565,18 @@ class CLIEngine {
baseConfig: options.baseConfig || null,
cliConfig: createConfigDataFromOptions(options),
cwd: options.cwd,
+ ignorePath: options.ignorePath,
resolvePluginsRelativeTo: options.resolvePluginsRelativeTo,
rulePaths: options.rulePaths,
specificConfigPath: options.configFile,
useEslintrc: options.useEslintrc
});
- const ignoredPaths = new IgnoredPaths(options);
const fileEnumerator = new FileEnumerator({
configArrayFactory,
cwd: options.cwd,
extensions: options.extensions,
globInputPaths: options.globInputPaths,
- ignore: options.ignore,
- ignoredPaths
+ ignore: options.ignore
});
const lintResultCache =
options.cache ? new LintResultCache(cacheFilePath) : null;
@@ -577,8 +590,8 @@ class CLIEngine {
additionalPluginPool,
cacheFilePath,
configArrayFactory,
+ defaultIgnores: IgnorePattern.createDefaultIgnore(options.cwd),
fileEnumerator,
- ignoredPaths,
lastConfigArrays,
lintResultCache,
linter,
@@ -835,7 +848,6 @@ class CLIEngine {
const {
configArrayFactory,
fileEnumerator,
- ignoredPaths,
lastConfigArrays,
linter,
options: {
@@ -852,7 +864,7 @@ class CLIEngine {
// Clear the last used config arrays.
lastConfigArrays.length = 0;
- if (resolvedFilename && ignoredPaths.contains(resolvedFilename)) {
+ if (resolvedFilename && this.isPathIgnored(resolvedFilename)) {
if (warnIgnored) {
results.push(createIgnoreResult(resolvedFilename, cwd));
}
@@ -926,9 +938,23 @@ class CLIEngine {
* @returns {boolean} Whether or not the given path is ignored.
*/
isPathIgnored(filePath) {
- const { ignoredPaths } = internalSlotsMap.get(this);
+ const {
+ configArrayFactory,
+ defaultIgnores,
+ options: { cwd, ignore }
+ } = internalSlotsMap.get(this);
+ const absolutePath = path.resolve(cwd, filePath);
+
+ if (ignore) {
+ const config = configArrayFactory
+ .getConfigArrayForFile(absolutePath)
+ .extractConfig(absolutePath);
+ const ignores = config.ignores || defaultIgnores;
+
+ return ignores(absolutePath);
+ }
- return ignoredPaths.contains(filePath);
+ return defaultIgnores(absolutePath);
}
/**
diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js b/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js
index cf529b6ee6..c444031bcb 100644
--- a/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js
+++ b/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js
@@ -17,6 +17,12 @@
* Create a `ConfigArray` instance from a config file which is on a given
* directory. This tries to load `.eslintrc.*` or `package.json`. If not
* found, returns an empty `ConfigArray`.
+ * - `loadESLintIgnore(filePath)`
+ * Create a `ConfigArray` instance from a config file that is `.eslintignore`
+ * format. This is to handle `--ignore-path` option.
+ * - `loadDefaultESLintIgnore()`
+ * Create a `ConfigArray` instance from `.eslintignore` or `package.json` in
+ * the current working directory.
*
* `ConfigArrayFactory` class has the responsibility that loads configuration
* files, including loading `extends`, `parser`, and `plugins`. The created
@@ -40,7 +46,12 @@ const stripComments = require("strip-json-comments");
const { validateConfigSchema } = require("../shared/config-validator");
const naming = require("../shared/naming");
const ModuleResolver = require("../shared/relative-module-resolver");
-const { ConfigArray, ConfigDependency, OverrideTester } = require("./config-array");
+const {
+ ConfigArray,
+ ConfigDependency,
+ IgnorePattern,
+ OverrideTester
+} = require("./config-array");
const debug = require("debug")("eslint:config-array-factory");
//------------------------------------------------------------------------------
@@ -222,6 +233,26 @@ function loadPackageJSONConfigFile(filePath) {
}
/**
+ * Loads a `.eslintignore` from a file.
+ * @param {string} filePath The filename to load.
+ * @returns {string[]} The ignore patterns from the file.
+ * @private
+ */
+function loadESLintIgnoreFile(filePath) {
+ debug(`Loading .eslintignore file: ${filePath}`);
+
+ try {
+ return readFile(filePath)
+ .split(/\r?\n/gu)
+ .filter(line => line.trim() !== "" && !line.startsWith("#"));
+ } catch (e) {
+ debug(`Error reading .eslintignore file: ${filePath}`);
+ e.message = `Cannot read .eslintignore file: ${filePath}\nError: ${e.message}`;
+ throw e;
+ }
+}
+
+/**
* Creates an error to notify about a missing config to extend from.
* @param {string} configName The name of the missing config.
* @param {string} importerName The name of the config that imported the missing config
@@ -404,6 +435,54 @@ class ConfigArrayFactory {
}
/**
+ * Load `.eslintignore` file.
+ * @param {string} filePath The path to a `.eslintignore` file to load.
+ * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
+ */
+ loadESLintIgnore(filePath) {
+ const { cwd } = internalSlotsMap.get(this);
+ const absolutePath = path.resolve(cwd, filePath);
+ const name = path.relative(cwd, absolutePath);
+ const ignorePatterns = loadESLintIgnoreFile(absolutePath);
+
+ return createConfigArray(
+ this._normalizeESLintIgnoreData(ignorePatterns, absolutePath, name)
+ );
+ }
+
+ /**
+ * Load `.eslintignore` file in the current working directory.
+ * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
+ */
+ loadDefaultESLintIgnore() {
+ const { cwd } = internalSlotsMap.get(this);
+ const eslintIgnorePath = path.resolve(cwd, ".eslintignore");
+ const packageJsonPath = path.resolve(cwd, "package.json");
+
+ if (fs.existsSync(eslintIgnorePath)) {
+ return this.loadESLintIgnore(eslintIgnorePath);
+ }
+ if (fs.existsSync(packageJsonPath)) {
+ const data = loadJSONConfigFile(packageJsonPath);
+
+ if (Object.hasOwnProperty.call(data, "eslintIgnore")) {
+ if (!Array.isArray(data.eslintIgnore)) {
+ throw new Error("Package.json eslintIgnore property requires an array of paths");
+ }
+ return createConfigArray(
+ this._normalizeESLintIgnoreData(
+ data.eslintIgnore,
+ packageJsonPath,
+ "eslintIgnore in package.json"
+ )
+ );
+ }
+ }
+
+ return new ConfigArray();
+ }
+
+ /**
* Load a given config file.
* @param {string} filePath The path to a config file.
* @param {string} name The config name.
@@ -452,6 +531,30 @@ class ConfigArrayFactory {
}
/**
+ * Normalize a given `.eslintignore` data to config array elements.
+ * @param {string[]} ignorePatterns The patterns to ignore files.
+ * @param {string|undefined} filePath The file path of this config.
+ * @param {string|undefined} name The name of this config.
+ * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
+ * @private
+ */
+ *_normalizeESLintIgnoreData(ignorePatterns, filePath, name) {
+ const elements = this._normalizeObjectConfigData(
+ { ignorePatterns },
+ filePath,
+ name
+ );
+
+ // Set `ignorePattern.loose` flag for backward compatibility.
+ for (const element of elements) {
+ if (element.ignorePattern) {
+ element.ignorePattern.loose = true;
+ }
+ yield element;
+ }
+ }
+
+ /**
* Normalize a given config to an array.
* @param {ConfigData} configData The config data to normalize.
* @param {string|undefined} providedFilePath The file path of this config.
@@ -494,6 +597,9 @@ class ConfigArrayFactory {
if (element.criteria) {
element.criteria.basePath = basePath;
}
+ if (element.ignorePattern) {
+ element.ignorePattern.basePath = basePath;
+ }
/*
* Merge the criteria; this is for only file extension processors in
@@ -526,6 +632,7 @@ class ConfigArrayFactory {
env,
extends: extend,
globals,
+ ignorePatterns,
noInlineConfig,
parser: parserName,
parserOptions,
@@ -541,6 +648,10 @@ class ConfigArrayFactory {
name
) {
const extendList = Array.isArray(extend) ? extend : [extend];
+ const ignorePattern = ignorePatterns && new IgnorePattern(
+ Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns],
+ filePath ? path.dirname(filePath) : internalSlotsMap.get(this).cwd
+ );
// Flatten `extends`.
for (const extendName of extendList.filter(Boolean)) {
@@ -569,6 +680,7 @@ class ConfigArrayFactory {
criteria: null,
env,
globals,
+ ignorePattern,
noInlineConfig,
parser,
parserOptions,
diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js b/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js
index 089ff305a2..4fae8deaca 100644
--- a/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js
+++ b/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js
@@ -31,6 +31,7 @@
//------------------------------------------------------------------------------
const { ExtractedConfig } = require("./extracted-config");
+const { IgnorePattern } = require("./ignore-pattern");
//------------------------------------------------------------------------------
// Helpers
@@ -54,6 +55,7 @@ const { ExtractedConfig } = require("./extracted-config");
* @property {InstanceType<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
* @property {Record<string, boolean>|undefined} env The environment settings.
* @property {Record<string, GlobalConf>|undefined} globals The global variable settings.
+ * @property {IgnorePattern|undefined} ignorePattern The ignore patterns.
* @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
* @property {DependentParser|undefined} parser The parser loader.
* @property {Object|undefined} parserOptions The parser options.
@@ -231,6 +233,7 @@ function mergeRuleConfigs(target, source) {
*/
function createConfig(instance, indices) {
const config = new ExtractedConfig();
+ const ignorePatterns = [];
// Merge elements.
for (const index of indices) {
@@ -260,6 +263,11 @@ function createConfig(instance, indices) {
config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
}
+ // Collect ignorePatterns
+ if (element.ignorePattern) {
+ ignorePatterns.push(element.ignorePattern);
+ }
+
// Merge others.
mergeWithoutOverwrite(config.env, element.env);
mergeWithoutOverwrite(config.globals, element.globals);
@@ -269,6 +277,11 @@ function createConfig(instance, indices) {
mergeRuleConfigs(config.rules, element.rules);
}
+ // Create the predicate function for ignore patterns.
+ if (ignorePatterns.length > 0) {
+ config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
+ }
+
return config;
}
diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array/extracted-config.js b/tools/node_modules/eslint/lib/cli-engine/config-array/extracted-config.js
index 66858313ba..b27d6ffb18 100644
--- a/tools/node_modules/eslint/lib/cli-engine/config-array/extracted-config.js
+++ b/tools/node_modules/eslint/lib/cli-engine/config-array/extracted-config.js
@@ -16,6 +16,8 @@
*/
"use strict";
+const { IgnorePattern } = require("./ignore-pattern");
+
// For VSCode intellisense
/** @typedef {import("../../shared/types").ConfigData} ConfigData */
/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
@@ -24,6 +26,17 @@
/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
/**
+ * Check if `xs` starts with `ys`.
+ * @template T
+ * @param {T[]} xs The array to check.
+ * @param {T[]} ys The array that may be the first part of `xs`.
+ * @returns {boolean} `true` if `xs` starts with `ys`.
+ */
+function startsWith(xs, ys) {
+ return xs.length >= ys.length && ys.every((y, i) => y === xs[i]);
+}
+
+/**
* The class for extracted config data.
*/
class ExtractedConfig {
@@ -48,6 +61,12 @@ class ExtractedConfig {
this.globals = {};
/**
+ * The glob patterns that ignore to lint.
+ * @type {(((filePath:string, dot?:boolean) => boolean) & { basePath:string; patterns:string[] }) | undefined}
+ */
+ this.ignores = void 0;
+
+ /**
* The flag that disables directive comments.
* @type {boolean|undefined}
*/
@@ -106,11 +125,19 @@ class ExtractedConfig {
configNameOfNoInlineConfig: _ignore1,
processor: _ignore2,
/* eslint-enable no-unused-vars */
+ ignores,
...config
} = this;
config.parser = config.parser && config.parser.filePath;
config.plugins = Object.keys(config.plugins).filter(Boolean).reverse();
+ config.ignorePatterns = ignores ? ignores.patterns : [];
+
+ // Strip the default patterns from `ignorePatterns`.
+ if (startsWith(config.ignorePatterns, IgnorePattern.DefaultPatterns)) {
+ config.ignorePatterns =
+ config.ignorePatterns.slice(IgnorePattern.DefaultPatterns.length);
+ }
return config;
}
diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array/ignore-pattern.js b/tools/node_modules/eslint/lib/cli-engine/config-array/ignore-pattern.js
new file mode 100644
index 0000000000..6140194433
--- /dev/null
+++ b/tools/node_modules/eslint/lib/cli-engine/config-array/ignore-pattern.js
@@ -0,0 +1,231 @@
+/**
+ * @fileoverview `IgnorePattern` class.
+ *
+ * `IgnorePattern` class has the set of glob patterns and the base path.
+ *
+ * It provides two static methods.
+ *
+ * - `IgnorePattern.createDefaultIgnore(cwd)`
+ * Create the default predicate function.
+ * - `IgnorePattern.createIgnore(ignorePatterns)`
+ * Create the predicate function from multiple `IgnorePattern` objects.
+ *
+ * It provides two properties and a method.
+ *
+ * - `patterns`
+ * The glob patterns that ignore to lint.
+ * - `basePath`
+ * The base path of the glob patterns. If absolute paths existed in the
+ * glob patterns, those are handled as relative paths to the base path.
+ * - `getPatternsRelativeTo(basePath)`
+ * Get `patterns` as modified for a given base path. It modifies the
+ * absolute paths in the patterns as prepending the difference of two base
+ * paths.
+ *
+ * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes
+ * `ignorePatterns` properties.
+ *
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("assert");
+const path = require("path");
+const ignore = require("ignore");
+const debug = require("debug")("eslint:ignore-pattern");
+
+/** @typedef {ReturnType<import("ignore").default>} Ignore */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Get the path to the common ancestor directory of given paths.
+ * @param {string[]} sourcePaths The paths to calculate the common ancestor.
+ * @returns {string} The path to the common ancestor directory.
+ */
+function getCommonAncestorPath(sourcePaths) {
+ let result = sourcePaths[0];
+
+ for (let i = 1; i < sourcePaths.length; ++i) {
+ const a = result;
+ const b = sourcePaths[i];
+
+ // Set the shorter one (it's the common ancestor if one includes the other).
+ result = a.length < b.length ? a : b;
+
+ // Set the common ancestor.
+ for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) {
+ if (a[j] !== b[j]) {
+ result = a.slice(0, lastSepPos);
+ break;
+ }
+ if (a[j] === path.sep) {
+ lastSepPos = j;
+ }
+ }
+ }
+
+ return result || path.sep;
+}
+
+/**
+ * Make relative path.
+ * @param {string} from The source path to get relative path.
+ * @param {string} to The destination path to get relative path.
+ * @returns {string} The relative path.
+ */
+function relative(from, to) {
+ const relPath = path.relative(from, to);
+
+ if (path.sep === "/") {
+ return relPath;
+ }
+ return relPath.split(path.sep).join("/");
+}
+
+/**
+ * Get the trailing slash if existed.
+ * @param {string} filePath The path to check.
+ * @returns {string} The trailing slash if existed.
+ */
+function dirSuffix(filePath) {
+ const isDir = (
+ filePath.endsWith(path.sep) ||
+ (process.platform === "win32" && filePath.endsWith("/"))
+ );
+
+ return isDir ? "/" : "";
+}
+
+const DefaultPatterns = Object.freeze(["/node_modules/*", "/bower_components/*"]);
+const DotPatterns = Object.freeze([".*", "!../"]);
+
+//------------------------------------------------------------------------------
+// Public
+//------------------------------------------------------------------------------
+
+class IgnorePattern {
+
+ /**
+ * The default patterns.
+ * @type {string[]}
+ */
+ static get DefaultPatterns() {
+ return DefaultPatterns;
+ }
+
+ /**
+ * Create the default predicate function.
+ * @param {string} cwd The current working directory.
+ * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}}
+ * The preficate function.
+ * The first argument is an absolute path that is checked.
+ * The second argument is the flag to not ignore dotfiles.
+ * If the predicate function returned `true`, it means the path should be ignored.
+ */
+ static createDefaultIgnore(cwd) {
+ return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]);
+ }
+
+ /**
+ * Create the predicate function from multiple `IgnorePattern` objects.
+ * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns.
+ * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}}
+ * The preficate function.
+ * The first argument is an absolute path that is checked.
+ * The second argument is the flag to not ignore dotfiles.
+ * If the predicate function returned `true`, it means the path should be ignored.
+ */
+ static createIgnore(ignorePatterns) {
+ debug("Create with: %o", ignorePatterns);
+
+ const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath));
+ const patterns = [].concat(
+ ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath))
+ );
+ const ig = ignore().add([...DotPatterns, ...patterns]);
+ const dotIg = ignore().add(patterns);
+
+ debug(" processed: %o", { basePath, patterns });
+
+ return Object.assign(
+ (filePath, dot = false) => {
+ assert(path.isAbsolute(filePath), "'filePath' should be an absolute path.");
+ const relPathRaw = relative(basePath, filePath);
+ const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath));
+ const adoptedIg = dot ? dotIg : ig;
+ const result = relPath !== "" && adoptedIg.ignores(relPath);
+
+ debug("Check", { filePath, dot, relativePath: relPath, result });
+ return result;
+ },
+ { basePath, patterns }
+ );
+ }
+
+ /**
+ * Initialize a new `IgnorePattern` instance.
+ * @param {string[]} patterns The glob patterns that ignore to lint.
+ * @param {string} basePath The base path of `patterns`.
+ */
+ constructor(patterns, basePath) {
+ assert(path.isAbsolute(basePath), "'basePath' should be an absolute path.");
+
+ /**
+ * The glob patterns that ignore to lint.
+ * @type {string[]}
+ */
+ this.patterns = patterns;
+
+ /**
+ * The base path of `patterns`.
+ * @type {string}
+ */
+ this.basePath = basePath;
+
+ /**
+ * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`.
+ *
+ * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility.
+ * It's `false` as-is for `ignorePatterns` property in config files.
+ * @type {boolean}
+ */
+ this.loose = false;
+ }
+
+ /**
+ * Get `patterns` as modified for a given base path. It modifies the
+ * absolute paths in the patterns as prepending the difference of two base
+ * paths.
+ * @param {string} newBasePath The base path.
+ * @returns {string[]} Modifired patterns.
+ */
+ getPatternsRelativeTo(newBasePath) {
+ assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path.");
+ const { basePath, loose, patterns } = this;
+
+ if (newBasePath === basePath) {
+ return patterns;
+ }
+ const prefix = `/${relative(newBasePath, basePath)}`;
+
+ return patterns.map(pattern => {
+ const negative = pattern.startsWith("!");
+ const head = negative ? "!" : "";
+ const body = negative ? pattern.slice(1) : pattern;
+
+ if (body.startsWith("/") || body.startsWith("../")) {
+ return `${head}${prefix}${body}`;
+ }
+ return loose ? pattern : `${head}${prefix}/**/${body}`;
+ });
+ }
+}
+
+module.exports = { IgnorePattern };
diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array/index.js b/tools/node_modules/eslint/lib/cli-engine/config-array/index.js
index de8831906f..928d76c83a 100644
--- a/tools/node_modules/eslint/lib/cli-engine/config-array/index.js
+++ b/tools/node_modules/eslint/lib/cli-engine/config-array/index.js
@@ -7,12 +7,14 @@
const { ConfigArray, getUsedExtractedConfigs } = require("./config-array");
const { ConfigDependency } = require("./config-dependency");
const { ExtractedConfig } = require("./extracted-config");
+const { IgnorePattern } = require("./ignore-pattern");
const { OverrideTester } = require("./override-tester");
module.exports = {
ConfigArray,
ConfigDependency,
ExtractedConfig,
+ IgnorePattern,
OverrideTester,
getUsedExtractedConfigs
};
diff --git a/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js b/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js
index 38f55de039..700f8009cf 100644
--- a/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js
+++ b/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js
@@ -40,8 +40,8 @@ const getGlobParent = require("glob-parent");
const isGlob = require("is-glob");
const { escapeRegExp } = require("lodash");
const { Minimatch } = require("minimatch");
+const { IgnorePattern } = require("./config-array");
const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
-const { IgnoredPaths } = require("./ignored-paths");
const debug = require("debug")("eslint:file-enumerator");
//------------------------------------------------------------------------------
@@ -64,7 +64,6 @@ const IGNORED = 2;
* @property {string[]} [extensions] The extensions to match files for directory patterns.
* @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
* @property {boolean} [ignore] The flag to check ignored files.
- * @property {IgnoredPaths} [ignoredPaths] The ignored paths.
* @property {string[]} [rulePaths] The value of `--rulesdir` option.
*/
@@ -92,8 +91,7 @@ const IGNORED = 2;
* @property {RegExp} extensionRegExp The RegExp to test if a string ends with specific file extensions.
* @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
* @property {boolean} ignoreFlag The flag to check ignored files.
- * @property {IgnoredPaths} ignoredPathsWithDotfiles The ignored paths but don't include dot files.
- * @property {IgnoredPaths} ignoredPaths The ignored paths.
+ * @property {(filePath:string, dot:boolean) => boolean} defaultIgnores The default predicate function to ignore files.
*/
/** @type {WeakMap<FileEnumerator, FileEnumeratorInternalSlots>} */
@@ -192,12 +190,12 @@ class FileEnumerator {
configArrayFactory = new CascadingConfigArrayFactory({ cwd }),
extensions = [".js"],
globInputPaths = true,
- ignore = true,
- ignoredPaths = new IgnoredPaths({ cwd, ignore })
+ ignore = true
} = {}) {
internalSlotsMap.set(this, {
configArrayFactory,
cwd,
+ defaultIgnores: IgnorePattern.createDefaultIgnore(cwd),
extensionRegExp: new RegExp(
`.\\.(?:${extensions
.map(ext => escapeRegExp(
@@ -210,12 +208,7 @@ class FileEnumerator {
"u"
),
globInputPaths,
- ignoreFlag: ignore,
- ignoredPaths,
- ignoredPathsWithDotfiles: new IgnoredPaths({
- ...ignoredPaths.options,
- dotfiles: true
- })
+ ignoreFlag: ignore
});
}
@@ -321,7 +314,7 @@ class FileEnumerator {
const { configArrayFactory } = internalSlotsMap.get(this);
const config = configArrayFactory.getConfigArrayForFile(filePath);
- const ignored = this._isIgnoredFile(filePath, { direct: true });
+ const ignored = this._isIgnoredFile(filePath, { config, direct: true });
const flag = ignored ? IGNORED : NONE;
return [{ config, filePath, flag }];
@@ -353,7 +346,7 @@ class FileEnumerator {
_iterateFilesWithGlob(pattern, dotfiles) {
debug(`Glob: ${pattern}`);
- const directoryPath = getGlobParent(pattern);
+ const directoryPath = path.resolve(getGlobParent(pattern));
const globPart = pattern.slice(directoryPath.length + 1);
/*
@@ -399,9 +392,18 @@ class FileEnumerator {
// Check if the file is matched.
if (stat && stat.isFile()) {
if (!config) {
- config = configArrayFactory.getConfigArrayForFile(filePath);
+ config = configArrayFactory.getConfigArrayForFile(
+ filePath,
+
+ /*
+ * We must ignore `ConfigurationNotFoundError` at this
+ * point because we don't know if target files exist in
+ * this directory.
+ */
+ { ignoreNotFoundError: true }
+ );
}
- const ignored = this._isIgnoredFile(filePath, options);
+ const ignored = this._isIgnoredFile(filePath, { ...options, config });
const flag = ignored ? IGNORED_SILENTLY : NONE;
const matched = options.selector
@@ -413,7 +415,11 @@ class FileEnumerator {
if (matched) {
debug(`Yield: ${filename}${ignored ? " but ignored" : ""}`);
- yield { config, filePath, flag };
+ yield {
+ config: configArrayFactory.getConfigArrayForFile(filePath),
+ filePath,
+ flag
+ };
} else {
debug(`Didn't match: ${filename}`);
}
@@ -431,24 +437,37 @@ class FileEnumerator {
* Check if a given file should be ignored.
* @param {string} filePath The path to a file to check.
* @param {Object} options Options
+ * @param {ConfigArray} [options.config] The config for this file.
* @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default.
* @param {boolean} [options.direct] If `true` then this is a direct specified file.
* @returns {boolean} `true` if the file should be ignored.
* @private
*/
- _isIgnoredFile(filePath, { dotfiles = false, direct = false }) {
+ _isIgnoredFile(filePath, {
+ config: providedConfig,
+ dotfiles = false,
+ direct = false
+ }) {
const {
- ignoreFlag,
- ignoredPaths,
- ignoredPathsWithDotfiles
+ configArrayFactory,
+ defaultIgnores,
+ ignoreFlag
} = internalSlotsMap.get(this);
- const adoptedIgnoredPaths = dotfiles
- ? ignoredPathsWithDotfiles
- : ignoredPaths;
- return ignoreFlag
- ? adoptedIgnoredPaths.contains(filePath)
- : (!direct && adoptedIgnoredPaths.contains(filePath, "default"));
+ if (ignoreFlag) {
+ const config =
+ providedConfig ||
+ configArrayFactory.getConfigArrayForFile(
+ filePath,
+ { ignoreNotFoundError: true }
+ );
+ const ignores =
+ config.extractConfig(filePath).ignores || defaultIgnores;
+
+ return ignores(filePath, dotfiles);
+ }
+
+ return !direct && defaultIgnores(filePath, dotfiles);
}
}
diff --git a/tools/node_modules/eslint/lib/cli-engine/ignored-paths.js b/tools/node_modules/eslint/lib/cli-engine/ignored-paths.js
deleted file mode 100644
index dec8e18604..0000000000
--- a/tools/node_modules/eslint/lib/cli-engine/ignored-paths.js
+++ /dev/null
@@ -1,363 +0,0 @@
-/**
- * @fileoverview Responsible for loading ignore config files and managing ignore patterns
- * @author Jonathan Rajavuori
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const fs = require("fs"),
- path = require("path"),
- ignore = require("ignore");
-
-const debug = require("debug")("eslint:ignored-paths");
-
-//------------------------------------------------------------------------------
-// Constants
-//------------------------------------------------------------------------------
-
-const ESLINT_IGNORE_FILENAME = ".eslintignore";
-
-/**
- * Adds `"*"` at the end of `"node_modules/"`,
- * so that subtle directories could be re-included by .gitignore patterns
- * such as `"!node_modules/should_not_ignored"`
- */
-const DEFAULT_IGNORE_DIRS = [
- "/node_modules/*",
- "/bower_components/*"
-];
-const DEFAULT_OPTIONS = {
- dotfiles: false,
- cwd: process.cwd()
-};
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Find a file in the current directory.
- * @param {string} cwd Current working directory
- * @param {string} name File name
- * @returns {string} Path of ignore file or an empty string.
- */
-function findFile(cwd, name) {
- const ignoreFilePath = path.resolve(cwd, name);
-
- return fs.existsSync(ignoreFilePath) && fs.statSync(ignoreFilePath).isFile() ? ignoreFilePath : "";
-}
-
-/**
- * Find an ignore file in the current directory.
- * @param {string} cwd Current working directory
- * @returns {string} Path of ignore file or an empty string.
- */
-function findIgnoreFile(cwd) {
- return findFile(cwd, ESLINT_IGNORE_FILENAME);
-}
-
-/**
- * Find an package.json file in the current directory.
- * @param {string} cwd Current working directory
- * @returns {string} Path of package.json file or an empty string.
- */
-function findPackageJSONFile(cwd) {
- return findFile(cwd, "package.json");
-}
-
-/**
- * Merge options with defaults
- * @param {Object} options Options to merge with DEFAULT_OPTIONS constant
- * @returns {Object} Merged options
- */
-function mergeDefaultOptions(options) {
- return Object.assign({}, DEFAULT_OPTIONS, options);
-}
-
-/* eslint-disable jsdoc/check-param-names, jsdoc/require-param */
-/**
- * Normalize the path separators in a given string.
- * On Windows environment, this replaces `\` by `/`.
- * Otherwrise, this does nothing.
- * @param {string} str The path string to normalize.
- * @returns {string} The normalized path.
- */
-const normalizePathSeps = path.sep === "/"
- ? (str => str)
- : ((seps, str) => str.replace(seps, "/")).bind(null, new RegExp(`\\${path.sep}`, "gu"));
-/* eslint-enable jsdoc/check-param-names, jsdoc/require-param */
-
-/**
- * Converts a glob pattern to a new glob pattern relative to a different directory
- * @param {string} globPattern The glob pattern, relative the the old base directory
- * @param {string} relativePathToOldBaseDir A relative path from the new base directory to the old one
- * @returns {string} A glob pattern relative to the new base directory
- */
-function relativize(globPattern, relativePathToOldBaseDir) {
- if (relativePathToOldBaseDir === "") {
- return globPattern;
- }
-
- const prefix = globPattern.startsWith("!") ? "!" : "";
- const globWithoutPrefix = globPattern.replace(/^!/u, "");
-
- if (globWithoutPrefix.startsWith("/")) {
- return `${prefix}/${normalizePathSeps(relativePathToOldBaseDir)}${globWithoutPrefix}`;
- }
-
- return globPattern;
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/**
- * IgnoredPaths class
- */
-class IgnoredPaths {
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @param {Object} providedOptions object containing 'ignore', 'ignorePath' and 'patterns' properties
- */
- constructor(providedOptions) {
- const options = mergeDefaultOptions(providedOptions);
-
- this.cache = {};
-
- this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
-
- this.ignoreFileDir = options.ignore !== false && options.ignorePath
- ? path.dirname(path.resolve(options.cwd, options.ignorePath))
- : options.cwd;
- this.options = options;
- this._baseDir = null;
-
- this.ig = {
- custom: ignore(),
- default: ignore()
- };
-
- this.defaultPatterns.forEach(pattern => this.addPatternRelativeToCwd(this.ig.default, pattern));
- if (options.dotfiles !== true) {
-
- /*
- * ignore files beginning with a dot, but not files in a parent or
- * ancestor directory (which in relative format will begin with `../`).
- */
- this.addPatternRelativeToCwd(this.ig.default, ".*");
- this.addPatternRelativeToCwd(this.ig.default, "!../");
- }
-
- /*
- * Add a way to keep track of ignored files. This was present in node-ignore
- * 2.x, but dropped for now as of 3.0.10.
- */
- this.ig.custom.ignoreFiles = [];
- this.ig.default.ignoreFiles = [];
-
- if (options.ignore !== false) {
- let ignorePath;
-
- if (options.ignorePath) {
- debug("Using specific ignore file");
-
- try {
- const stat = fs.statSync(options.ignorePath);
-
- if (!stat.isFile()) {
- throw new Error(`${options.ignorePath} is not a file`);
- }
- ignorePath = options.ignorePath;
- } catch (e) {
- e.message = `Cannot read ignore file: ${options.ignorePath}\nError: ${e.message}`;
- throw e;
- }
- } else {
- debug(`Looking for ignore file in ${options.cwd}`);
- ignorePath = findIgnoreFile(options.cwd);
-
- try {
- fs.statSync(ignorePath);
- debug(`Loaded ignore file ${ignorePath}`);
- } catch (e) {
- debug("Could not find ignore file in cwd");
- }
- }
-
- if (ignorePath) {
- debug(`Adding ${ignorePath}`);
- this.addIgnoreFile(this.ig.custom, ignorePath);
- this.addIgnoreFile(this.ig.default, ignorePath);
- } else {
- try {
-
- // if the ignoreFile does not exist, check package.json for eslintIgnore
- const packageJSONPath = findPackageJSONFile(options.cwd);
-
- if (packageJSONPath) {
- let packageJSONOptions;
-
- try {
- packageJSONOptions = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"));
- } catch (e) {
- debug("Could not read package.json file to check eslintIgnore property");
- e.messageTemplate = "failed-to-read-json";
- e.messageData = {
- path: packageJSONPath,
- message: e.message
- };
- throw e;
- }
-
- if (packageJSONOptions.eslintIgnore) {
- if (Array.isArray(packageJSONOptions.eslintIgnore)) {
- packageJSONOptions.eslintIgnore.forEach(pattern => {
- this.addPatternRelativeToIgnoreFile(this.ig.custom, pattern);
- this.addPatternRelativeToIgnoreFile(this.ig.default, pattern);
- });
- } else {
- throw new TypeError("Package.json eslintIgnore property requires an array of paths");
- }
- }
- }
- } catch (e) {
- debug("Could not find package.json to check eslintIgnore property");
- throw e;
- }
- }
-
- if (options.ignorePattern) {
- this.addPatternRelativeToCwd(this.ig.custom, options.ignorePattern);
- this.addPatternRelativeToCwd(this.ig.default, options.ignorePattern);
- }
- }
- }
-
- /*
- * If `ignoreFileDir` is a subdirectory of `cwd`, all paths will be normalized to be relative to `cwd`.
- * Otherwise, all paths will be normalized to be relative to `ignoreFileDir`.
- * This ensures that the final normalized ignore rule will not contain `..`, which is forbidden in
- * ignore rules.
- */
-
- addPatternRelativeToCwd(ig, pattern) {
- const baseDir = this.getBaseDir();
- const cookedPattern = baseDir === this.options.cwd
- ? pattern
- : relativize(pattern, path.relative(baseDir, this.options.cwd));
-
- ig.addPattern(cookedPattern);
- debug("addPatternRelativeToCwd:\n original = %j\n cooked = %j", pattern, cookedPattern);
- }
-
- addPatternRelativeToIgnoreFile(ig, pattern) {
- const baseDir = this.getBaseDir();
- const cookedPattern = baseDir === this.ignoreFileDir
- ? pattern
- : relativize(pattern, path.relative(baseDir, this.ignoreFileDir));
-
- ig.addPattern(cookedPattern);
- debug("addPatternRelativeToIgnoreFile:\n original = %j\n cooked = %j", pattern, cookedPattern);
- }
-
- // Detect the common ancestor
- getBaseDir() {
- if (!this._baseDir) {
- const a = path.resolve(this.options.cwd);
- const b = path.resolve(this.ignoreFileDir);
- let lastSepPos = 0;
-
- // Set the shorter one (it's the common ancestor if one includes the other).
- this._baseDir = a.length < b.length ? a : b;
-
- // Set the common ancestor.
- for (let i = 0; i < a.length && i < b.length; ++i) {
- if (a[i] !== b[i]) {
- this._baseDir = a.slice(0, lastSepPos);
- break;
- }
- if (a[i] === path.sep) {
- lastSepPos = i;
- }
- }
-
- // If it's only Windows drive letter, it needs \
- if (/^[A-Z]:$/u.test(this._baseDir)) {
- this._baseDir += "\\";
- }
-
- debug("baseDir = %j", this._baseDir);
- }
- return this._baseDir;
- }
-
- /**
- * read ignore filepath
- * @param {string} filePath file to add to ig
- * @returns {Array} raw ignore rules
- */
- readIgnoreFile(filePath) {
- if (typeof this.cache[filePath] === "undefined") {
- this.cache[filePath] = fs.readFileSync(filePath, "utf8").split(/\r?\n/gu).filter(Boolean);
- }
- return this.cache[filePath];
- }
-
- /**
- * add ignore file to node-ignore instance
- * @param {Object} ig instance of node-ignore
- * @param {string} filePath file to add to ig
- * @returns {void}
- */
- addIgnoreFile(ig, filePath) {
- ig.ignoreFiles.push(filePath);
- this
- .readIgnoreFile(filePath)
- .forEach(ignoreRule => this.addPatternRelativeToIgnoreFile(ig, ignoreRule));
- }
-
- /**
- * Determine whether a file path is included in the default or custom ignore patterns
- * @param {string} filepath Path to check
- * @param {string} [category=undefined] check 'default', 'custom' or both (undefined)
- * @returns {boolean} true if the file path matches one or more patterns, false otherwise
- */
- contains(filepath, category) {
- const isDir = filepath.endsWith(path.sep) ||
- (path.sep === "\\" && filepath.endsWith("/"));
- let result = false;
- const basePath = this.getBaseDir();
- const absolutePath = path.resolve(this.options.cwd, filepath);
- let relativePath = path.relative(basePath, absolutePath);
-
- if (relativePath) {
- if (isDir) {
- relativePath += path.sep;
- }
- if (typeof category === "undefined") {
- result =
- (this.ig.default.filter([relativePath]).length === 0) ||
- (this.ig.custom.filter([relativePath]).length === 0);
- } else {
- result =
- (this.ig[category].filter([relativePath]).length === 0);
- }
- }
- debug("contains:");
- debug(" target = %j", filepath);
- debug(" base = %j", basePath);
- debug(" relative = %j", relativePath);
- debug(" result = %j", result);
-
- return result;
-
- }
-}
-
-module.exports = { IgnoredPaths };
diff --git a/tools/node_modules/eslint/lib/init/config-initializer.js b/tools/node_modules/eslint/lib/init/config-initializer.js
index 48e56ce526..28dfad194a 100644
--- a/tools/node_modules/eslint/lib/init/config-initializer.js
+++ b/tools/node_modules/eslint/lib/init/config-initializer.js
@@ -291,6 +291,7 @@ function processAnswers(answers) {
jsx: true
};
config.plugins = ["react"];
+ config.extends.push("plugin:react/recommended");
} else if (answers.framework === "vue") {
config.plugins = ["vue"];
config.extends.push("plugin:vue/essential");
@@ -522,9 +523,9 @@ function promptUser() {
name: "styleguide",
message: "Which style guide do you want to follow?",
choices: [
- { name: "Airbnb (https://github.com/airbnb/javascript)", value: "airbnb" },
- { name: "Standard (https://github.com/standard/standard)", value: "standard" },
- { name: "Google (https://github.com/google/eslint-config-google)", value: "google" }
+ { name: "Airbnb: https://github.com/airbnb/javascript", value: "airbnb" },
+ { name: "Standard: https://github.com/standard/standard", value: "standard" },
+ { name: "Google: https://github.com/google/eslint-config-google", value: "google" }
],
when(answers) {
answers.packageJsonExists = npmUtils.checkPackageJson();
diff --git a/tools/node_modules/eslint/lib/linter/report-translator.js b/tools/node_modules/eslint/lib/linter/report-translator.js
index 8c9ed007a2..eef5165585 100644
--- a/tools/node_modules/eslint/lib/linter/report-translator.js
+++ b/tools/node_modules/eslint/lib/linter/report-translator.js
@@ -26,6 +26,7 @@ const interpolate = require("./interpolate");
* @property {Object} [data] Optional data to use to fill in placeholders in the
* message.
* @property {Function} [fix] The function to call that creates a fix command.
+ * @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes.
*/
/**
@@ -34,14 +35,15 @@ const interpolate = require("./interpolate");
* @property {string} ruleId
* @property {(0|1|2)} severity
* @property {(string|undefined)} message
- * @property {(string|undefined)} messageId
+ * @property {(string|undefined)} [messageId]
* @property {number} line
* @property {number} column
- * @property {(number|undefined)} endLine
- * @property {(number|undefined)} endColumn
+ * @property {(number|undefined)} [endLine]
+ * @property {(number|undefined)} [endColumn]
* @property {(string|null)} nodeType
* @property {string} source
- * @property {({text: string, range: (number[]|null)}|null)} fix
+ * @property {({text: string, range: (number[]|null)}|null)} [fix]
+ * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions]
*/
//------------------------------------------------------------------------------
@@ -183,6 +185,29 @@ function normalizeFixes(descriptor, sourceCode) {
}
/**
+ * Gets an array of suggestion objects from the given descriptor.
+ * @param {MessageDescriptor} descriptor The report descriptor.
+ * @param {SourceCode} sourceCode The source code object to get text between fixes.
+ * @param {Object} messages Object of meta messages for the rule.
+ * @returns {Array<SuggestionResult>} The suggestions for the descriptor
+ */
+function mapSuggestions(descriptor, sourceCode, messages) {
+ if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) {
+ return [];
+ }
+
+ return descriptor.suggest.map(suggestInfo => {
+ const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
+
+ return {
+ ...suggestInfo,
+ desc: interpolate(computedDesc, suggestInfo.data),
+ fix: normalizeFixes(suggestInfo, sourceCode)
+ };
+ });
+}
+
+/**
* Creates information about the report from a descriptor
* @param {Object} options Information about the problem
* @param {string} options.ruleId Rule ID
@@ -192,6 +217,7 @@ function normalizeFixes(descriptor, sourceCode) {
* @param {string} [options.messageId] The error message ID.
* @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
* @param {{text: string, range: (number[]|null)}} options.fix The fix object
+ * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects
* @returns {function(...args): ReportInfo} Function that returns information about the report
*/
function createProblem(options) {
@@ -221,10 +247,48 @@ function createProblem(options) {
problem.fix = options.fix;
}
+ if (options.suggestions && options.suggestions.length > 0) {
+ problem.suggestions = options.suggestions;
+ }
+
return problem;
}
/**
+ * Validates that suggestions are properly defined. Throws if an error is detected.
+ * @param {Array<{ desc?: string, messageId?: string }>} suggest The incoming suggest data.
+ * @param {Object} messages Object of meta messages for the rule.
+ * @returns {void}
+ */
+function validateSuggestions(suggest, messages) {
+ if (suggest && Array.isArray(suggest)) {
+ suggest.forEach(suggestion => {
+ if (suggestion.messageId) {
+ const { messageId } = suggestion;
+
+ if (!messages) {
+ throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`);
+ }
+
+ if (!messages[messageId]) {
+ throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
+ }
+
+ if (suggestion.desc) {
+ throw new TypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one.");
+ }
+ } else if (!suggestion.desc) {
+ throw new TypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`");
+ }
+
+ if (typeof suggestion.fix !== "function") {
+ throw new TypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`);
+ }
+ });
+ }
+}
+
+/**
* Returns a function that converts the arguments of a `context.report` call from a rule into a reported
* problem for the Node.js API.
* @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem
@@ -242,17 +306,17 @@ module.exports = function createReportTranslator(metadata) {
*/
return (...args) => {
const descriptor = normalizeMultiArgReportCall(...args);
+ const messages = metadata.messageIds;
assertValidNodeInfo(descriptor);
let computedMessage;
if (descriptor.messageId) {
- if (!metadata.messageIds) {
+ if (!messages) {
throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata.");
}
const id = descriptor.messageId;
- const messages = metadata.messageIds;
if (descriptor.message) {
throw new TypeError("context.report() called with a message and a messageId. Please only pass one.");
@@ -267,6 +331,7 @@ module.exports = function createReportTranslator(metadata) {
throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem.");
}
+ validateSuggestions(descriptor.suggest, messages);
return createProblem({
ruleId: metadata.ruleId,
@@ -275,7 +340,8 @@ module.exports = function createReportTranslator(metadata) {
message: interpolate(computedMessage, descriptor.data),
messageId: descriptor.messageId,
loc: normalizeReportLoc(descriptor),
- fix: metadata.disableFixes ? null : normalizeFixes(descriptor, metadata.sourceCode)
+ fix: metadata.disableFixes ? null : normalizeFixes(descriptor, metadata.sourceCode),
+ suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, metadata.sourceCode, messages)
});
};
};
diff --git a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js
index e57cd09bd8..b4b3b2bec8 100644
--- a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js
+++ b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js
@@ -349,7 +349,7 @@ class RuleTester {
filename = item.filename;
}
- if (Object.prototype.hasOwnProperty.call(item, "options")) {
+ if (hasOwnProperty(item, "options")) {
assert(Array.isArray(item.options), "options must be an array");
config.rules[ruleName] = [1].concat(item.options);
} else {
@@ -577,21 +577,55 @@ class RuleTester {
assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`);
}
- if (Object.prototype.hasOwnProperty.call(error, "line")) {
+ if (hasOwnProperty(error, "line")) {
assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`);
}
- if (Object.prototype.hasOwnProperty.call(error, "column")) {
+ if (hasOwnProperty(error, "column")) {
assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`);
}
- if (Object.prototype.hasOwnProperty.call(error, "endLine")) {
+ if (hasOwnProperty(error, "endLine")) {
assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`);
}
- if (Object.prototype.hasOwnProperty.call(error, "endColumn")) {
+ if (hasOwnProperty(error, "endColumn")) {
assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`);
}
+
+ if (hasOwnProperty(error, "suggestions")) {
+
+ // Support asserting there are no suggestions
+ if (!error.suggestions) {
+ assert.strictEqual(message.suggestions, error.suggestions, `Error should have no suggestions on error with message: "${message.message}"`);
+ } else {
+ assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`);
+ assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`);
+
+ error.suggestions.forEach((expectedSuggestion, index) => {
+ const actualSuggestion = message.suggestions[index];
+
+ /**
+ * Tests equality of a suggestion key if that key is defined in the expected output.
+ * @param {string} key Key to validate from the suggestion object
+ * @returns {void}
+ */
+ function assertSuggestionKeyEquals(key) {
+ if (hasOwnProperty(expectedSuggestion, key)) {
+ assert.deepStrictEqual(actualSuggestion[key], expectedSuggestion[key], `Error suggestion at index: ${index} should have desc of: "${actualSuggestion[key]}"`);
+ }
+ }
+ assertSuggestionKeyEquals("desc");
+ assertSuggestionKeyEquals("messageId");
+
+ if (hasOwnProperty(expectedSuggestion, "output")) {
+ const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
+
+ assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
+ }
+ });
+ }
+ }
} else {
// Message was an unexpected type
@@ -600,7 +634,7 @@ class RuleTester {
}
}
- if (Object.prototype.hasOwnProperty.call(item, "output")) {
+ if (hasOwnProperty(item, "output")) {
if (item.output === null) {
assert.strictEqual(
result.output,
diff --git a/tools/node_modules/eslint/lib/rules/camelcase.js b/tools/node_modules/eslint/lib/rules/camelcase.js
index cdc16fab9f..a06c7f9404 100644
--- a/tools/node_modules/eslint/lib/rules/camelcase.js
+++ b/tools/node_modules/eslint/lib/rules/camelcase.js
@@ -28,6 +28,10 @@ module.exports = {
type: "boolean",
default: false
},
+ ignoreImports: {
+ type: "boolean",
+ default: false
+ },
properties: {
enum: ["always", "never"]
},
@@ -56,6 +60,7 @@ module.exports = {
const options = context.options[0] || {};
let properties = options.properties || "";
const ignoreDestructuring = options.ignoreDestructuring;
+ const ignoreImports = options.ignoreImports;
const allow = options.allow || [];
if (properties !== "always" && properties !== "never") {
@@ -79,7 +84,7 @@ module.exports = {
function isUnderscored(name) {
// if there's an underscore, it might be A_CONSTANT, which is okay
- return name.indexOf("_") > -1 && name !== name.toUpperCase();
+ return name.includes("_") && name !== name.toUpperCase();
}
/**
@@ -89,9 +94,9 @@ module.exports = {
* @private
*/
function isAllowed(name) {
- return allow.findIndex(
+ return allow.some(
entry => name === entry || name.match(new RegExp(entry, "u"))
- ) !== -1;
+ );
}
/**
@@ -127,7 +132,7 @@ module.exports = {
* @private
*/
function report(node) {
- if (reported.indexOf(node) < 0) {
+ if (!reported.includes(node)) {
reported.push(node);
context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
}
@@ -209,10 +214,18 @@ module.exports = {
}
// Check if it's an import specifier
- } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
+ } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
+
+ if (node.parent.type === "ImportSpecifier" && ignoreImports) {
+ return;
+ }
// Report only if the local imported identifier is underscored
- if (node.parent.local && node.parent.local.name === node.name && nameIsUnderscored) {
+ if (
+ node.parent.local &&
+ node.parent.local.name === node.name &&
+ nameIsUnderscored
+ ) {
report(node);
}
diff --git a/tools/node_modules/eslint/lib/rules/comma-dangle.js b/tools/node_modules/eslint/lib/rules/comma-dangle.js
index fb2d167c77..e22b7f3551 100644
--- a/tools/node_modules/eslint/lib/rules/comma-dangle.js
+++ b/tools/node_modules/eslint/lib/rules/comma-dangle.js
@@ -231,7 +231,7 @@ module.exports = {
if (astUtils.isCommaToken(trailingToken)) {
context.report({
node: lastItem,
- loc: trailingToken.loc.start,
+ loc: trailingToken.loc,
messageId: "unexpected",
fix(fixer) {
return fixer.remove(trailingToken);
@@ -267,7 +267,10 @@ module.exports = {
if (trailingToken.value !== ",") {
context.report({
node: lastItem,
- loc: trailingToken.loc.end,
+ loc: {
+ start: trailingToken.loc.end,
+ end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
+ },
messageId: "missing",
fix(fixer) {
return fixer.insertTextAfter(trailingToken, ",");
diff --git a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js
index bc8be964f4..a0bd7f48ce 100644
--- a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js
+++ b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js
@@ -153,10 +153,10 @@ module.exports = {
const property = node[propertyName];
- const before = sourceCode.getTokenBefore(property),
- first = sourceCode.getFirstToken(property),
- last = sourceCode.getLastToken(property),
- after = sourceCode.getTokenAfter(property);
+ const before = sourceCode.getTokenBefore(property, astUtils.isOpeningBracketToken),
+ first = sourceCode.getTokenAfter(before, { includeComments: true }),
+ after = sourceCode.getTokenAfter(property, astUtils.isClosingBracketToken),
+ last = sourceCode.getTokenBefore(after, { includeComments: true });
if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) {
diff --git a/tools/node_modules/eslint/lib/rules/curly.js b/tools/node_modules/eslint/lib/rules/curly.js
index 93c74d11fc..ee2fe4dceb 100644
--- a/tools/node_modules/eslint/lib/rules/curly.js
+++ b/tools/node_modules/eslint/lib/rules/curly.js
@@ -97,10 +97,15 @@ module.exports = {
* @private
*/
function isOneLiner(node) {
- const first = sourceCode.getFirstToken(node),
- last = sourceCode.getLastToken(node);
+ if (node.type === "EmptyStatement") {
+ return true;
+ }
+
+ const first = sourceCode.getFirstToken(node);
+ const last = sourceCode.getLastToken(node);
+ const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
- return first.loc.start.line === last.loc.end.line;
+ return first.loc.start.line === lastExcludingSemicolon.loc.end.line;
}
/**
@@ -240,7 +245,7 @@ module.exports = {
if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) {
expected = true;
} else if (multiOnly) {
- if (hasBlock && body.body.length === 1) {
+ if (hasBlock && body.body.length === 1 && !isLexicalDeclaration(body.body[0])) {
expected = false;
}
} else if (multiLine) {
diff --git a/tools/node_modules/eslint/lib/rules/function-call-argument-newline.js b/tools/node_modules/eslint/lib/rules/function-call-argument-newline.js
index 31ebc097c4..b6abbe95fa 100644
--- a/tools/node_modules/eslint/lib/rules/function-call-argument-newline.js
+++ b/tools/node_modules/eslint/lib/rules/function-call-argument-newline.js
@@ -70,6 +70,8 @@ module.exports = {
{ includeComments: true }
);
+ const hasLineCommentBefore = tokenBefore.type === "Line";
+
context.report({
node,
loc: {
@@ -77,7 +79,7 @@ module.exports = {
end: currentArgToken.loc.start
},
messageId: checker.messageId,
- fix: checker.createFix(currentArgToken, tokenBefore)
+ fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore)
});
}
}
diff --git a/tools/node_modules/eslint/lib/rules/grouped-accessor-pairs.js b/tools/node_modules/eslint/lib/rules/grouped-accessor-pairs.js
new file mode 100644
index 0000000000..a790f83750
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/grouped-accessor-pairs.js
@@ -0,0 +1,224 @@
+/**
+ * @fileoverview Rule to require grouped accessor pairs in object literals and classes
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
+ * @typedef {string|Token[]} Key
+ */
+
+/**
+ * Accessor nodes with the same key.
+ * @typedef {Object} AccessorData
+ * @property {Key} key Accessor's key
+ * @property {ASTNode[]} getters List of getter nodes.
+ * @property {ASTNode[]} setters List of setter nodes.
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not the given lists represent the equal tokens in the same order.
+ * Tokens are compared by their properties, not by instance.
+ * @param {Token[]} left First list of tokens.
+ * @param {Token[]} right Second list of tokens.
+ * @returns {boolean} `true` if the lists have same tokens.
+ */
+function areEqualTokenLists(left, right) {
+ if (left.length !== right.length) {
+ return false;
+ }
+
+ for (let i = 0; i < left.length; i++) {
+ const leftToken = left[i],
+ rightToken = right[i];
+
+ if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Checks whether or not the given keys are equal.
+ * @param {Key} left First key.
+ * @param {Key} right Second key.
+ * @returns {boolean} `true` if the keys are equal.
+ */
+function areEqualKeys(left, right) {
+ if (typeof left === "string" && typeof right === "string") {
+
+ // Statically computed names.
+ return left === right;
+ }
+ if (Array.isArray(left) && Array.isArray(right)) {
+
+ // Token lists.
+ return areEqualTokenLists(left, right);
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether or not a given node is of an accessor kind ('get' or 'set').
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is of an accessor kind.
+ */
+function isAccessorKind(node) {
+ return node.kind === "get" || node.kind === "set";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "require grouped accessor pairs in object literals and classes",
+ category: "Best Practices",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/grouped-accessor-pairs"
+ },
+
+ schema: [
+ {
+ enum: ["anyOrder", "getBeforeSet", "setBeforeGet"]
+ }
+ ],
+
+ messages: {
+ notGrouped: "Accessor pair {{ formerName }} and {{ latterName }} should be grouped.",
+ invalidOrder: "Expected {{ latterName }} to be before {{ formerName }}."
+ }
+ },
+
+ create(context) {
+ const order = context.options[0] || "anyOrder";
+ const sourceCode = context.getSourceCode();
+
+ /**
+ * Reports the given accessor pair.
+ * @param {string} messageId messageId to report.
+ * @param {ASTNode} formerNode getter/setter node that is defined before `latterNode`.
+ * @param {ASTNode} latterNode getter/setter node that is defined after `formerNode`.
+ * @returns {void}
+ * @private
+ */
+ function report(messageId, formerNode, latterNode) {
+ context.report({
+ node: latterNode,
+ messageId,
+ loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode),
+ data: {
+ formerName: astUtils.getFunctionNameWithKind(formerNode.value),
+ latterName: astUtils.getFunctionNameWithKind(latterNode.value)
+ }
+ });
+ }
+
+ /**
+ * Creates a new `AccessorData` object for the given getter or setter node.
+ * @param {ASTNode} node A getter or setter node.
+ * @returns {AccessorData} New `AccessorData` object that contains the given node.
+ * @private
+ */
+ function createAccessorData(node) {
+ const name = astUtils.getStaticPropertyName(node);
+ const key = (name !== null) ? name : sourceCode.getTokens(node.key);
+
+ return {
+ key,
+ getters: node.kind === "get" ? [node] : [],
+ setters: node.kind === "set" ? [node] : []
+ };
+ }
+
+ /**
+ * Merges the given `AccessorData` object into the given accessors list.
+ * @param {AccessorData[]} accessors The list to merge into.
+ * @param {AccessorData} accessorData The object to merge.
+ * @returns {AccessorData[]} The same instance with the merged object.
+ * @private
+ */
+ function mergeAccessorData(accessors, accessorData) {
+ const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
+
+ if (equalKeyElement) {
+ equalKeyElement.getters.push(...accessorData.getters);
+ equalKeyElement.setters.push(...accessorData.setters);
+ } else {
+ accessors.push(accessorData);
+ }
+
+ return accessors;
+ }
+
+ /**
+ * Checks accessor pairs in the given list of nodes.
+ * @param {ASTNode[]} nodes The list to check.
+ * @param {Function} shouldCheck – Predicate that returns `true` if the node should be checked.
+ * @returns {void}
+ * @private
+ */
+ function checkList(nodes, shouldCheck) {
+ const accessors = nodes
+ .filter(shouldCheck)
+ .filter(isAccessorKind)
+ .map(createAccessorData)
+ .reduce(mergeAccessorData, []);
+
+ for (const { getters, setters } of accessors) {
+
+ // Don't report accessor properties that have duplicate getters or setters.
+ if (getters.length === 1 && setters.length === 1) {
+ const [getter] = getters,
+ [setter] = setters,
+ getterIndex = nodes.indexOf(getter),
+ setterIndex = nodes.indexOf(setter),
+ formerNode = getterIndex < setterIndex ? getter : setter,
+ latterNode = getterIndex < setterIndex ? setter : getter;
+
+ if (Math.abs(getterIndex - setterIndex) > 1) {
+ report("notGrouped", formerNode, latterNode);
+ } else if (
+ (order === "getBeforeSet" && getterIndex > setterIndex) ||
+ (order === "setBeforeGet" && getterIndex < setterIndex)
+ ) {
+ report("invalidOrder", formerNode, latterNode);
+ }
+ }
+ }
+ }
+
+ return {
+ ObjectExpression(node) {
+ checkList(node.properties, n => n.type === "Property");
+ },
+ ClassBody(node) {
+ checkList(node.body, n => n.type === "MethodDefinition" && !n.static);
+ checkList(node.body, n => n.type === "MethodDefinition" && n.static);
+ }
+ };
+ }
+};
diff --git a/tools/node_modules/eslint/lib/rules/indent.js b/tools/node_modules/eslint/lib/rules/indent.js
index 94c83692b3..694cf7d9e6 100644
--- a/tools/node_modules/eslint/lib/rules/indent.js
+++ b/tools/node_modules/eslint/lib/rules/indent.js
@@ -1492,6 +1492,17 @@ module.exports = {
);
},
+ JSXSpreadAttribute(node) {
+ const openingCurly = sourceCode.getFirstToken(node);
+ const closingCurly = sourceCode.getLastToken(node);
+
+ offsets.setDesiredOffsets(
+ [openingCurly.range[1], closingCurly.range[0]],
+ openingCurly,
+ 1
+ );
+ },
+
"*"(node) {
const firstToken = sourceCode.getFirstToken(node);
diff --git a/tools/node_modules/eslint/lib/rules/index.js b/tools/node_modules/eslint/lib/rules/index.js
index 8b0abc4ee7..d3fbe41208 100644
--- a/tools/node_modules/eslint/lib/rules/index.js
+++ b/tools/node_modules/eslint/lib/rules/index.js
@@ -52,6 +52,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"generator-star-spacing": () => require("./generator-star-spacing"),
"getter-return": () => require("./getter-return"),
"global-require": () => require("./global-require"),
+ "grouped-accessor-pairs": () => require("./grouped-accessor-pairs"),
"guard-for-in": () => require("./guard-for-in"),
"handle-callback-err": () => require("./handle-callback-err"),
"id-blacklist": () => require("./id-blacklist"),
@@ -101,6 +102,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-console": () => require("./no-console"),
"no-const-assign": () => require("./no-const-assign"),
"no-constant-condition": () => require("./no-constant-condition"),
+ "no-constructor-return": () => require("./no-constructor-return"),
"no-continue": () => require("./no-continue"),
"no-control-regex": () => require("./no-control-regex"),
"no-debugger": () => require("./no-debugger"),
@@ -108,6 +110,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-div-regex": () => require("./no-div-regex"),
"no-dupe-args": () => require("./no-dupe-args"),
"no-dupe-class-members": () => require("./no-dupe-class-members"),
+ "no-dupe-else-if": () => require("./no-dupe-else-if"),
"no-dupe-keys": () => require("./no-dupe-keys"),
"no-duplicate-case": () => require("./no-duplicate-case"),
"no-duplicate-imports": () => require("./no-duplicate-imports"),
@@ -186,6 +189,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-self-assign": () => require("./no-self-assign"),
"no-self-compare": () => require("./no-self-compare"),
"no-sequences": () => require("./no-sequences"),
+ "no-setter-return": () => require("./no-setter-return"),
"no-shadow": () => require("./no-shadow"),
"no-shadow-restricted-names": () => require("./no-shadow-restricted-names"),
"no-spaced-func": () => require("./no-spaced-func"),
@@ -238,6 +242,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"prefer-arrow-callback": () => require("./prefer-arrow-callback"),
"prefer-const": () => require("./prefer-const"),
"prefer-destructuring": () => require("./prefer-destructuring"),
+ "prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
"prefer-named-capture-group": () => require("./prefer-named-capture-group"),
"prefer-numeric-literals": () => require("./prefer-numeric-literals"),
"prefer-object-spread": () => require("./prefer-object-spread"),
diff --git a/tools/node_modules/eslint/lib/rules/multiline-comment-style.js b/tools/node_modules/eslint/lib/rules/multiline-comment-style.js
index 6578a12012..fb50e1522e 100644
--- a/tools/node_modules/eslint/lib/rules/multiline-comment-style.js
+++ b/tools/node_modules/eslint/lib/rules/multiline-comment-style.js
@@ -43,17 +43,150 @@ module.exports = {
//----------------------------------------------------------------------
/**
- * Gets a list of comment lines in a group
- * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment
- * @returns {string[]} A list of comment lines
+ * Checks if a comment line is starred.
+ * @param {string} line A string representing a comment line.
+ * @returns {boolean} Whether or not the comment line is starred.
+ */
+ function isStarredCommentLine(line) {
+ return /^\s*\*/u.test(line);
+ }
+
+ /**
+ * Checks if a comment group is in starred-block form.
+ * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment.
+ * @returns {boolean} Whether or not the comment group is in starred block form.
+ */
+ function isStarredBlockComment([firstComment]) {
+ if (firstComment.type !== "Block") {
+ return false;
+ }
+
+ const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER);
+
+ // The first and last lines can only contain whitespace.
+ return lines.length > 0 && lines.every((line, i) => (i === 0 || i === lines.length - 1 ? /^\s*$/u : /^\s*\*/u).test(line));
+ }
+
+ /**
+ * Checks if a comment group is in JSDoc form.
+ * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment.
+ * @returns {boolean} Whether or not the comment group is in JSDoc form.
+ */
+ function isJSDocComment([firstComment]) {
+ if (firstComment.type !== "Block") {
+ return false;
+ }
+
+ const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER);
+
+ return /^\*\s*$/u.test(lines[0]) &&
+ lines.slice(1, -1).every(line => /^\s* /u.test(line)) &&
+ /^\s*$/u.test(lines[lines.length - 1]);
+ }
+
+ /**
+ * Processes a comment group that is currently in separate-line form, calculating the offset for each line.
+ * @param {Token[]} commentGroup A group of comments containing multiple line comments.
+ * @returns {string[]} An array of the processed lines.
+ */
+ function processSeparateLineComments(commentGroup) {
+ const allLinesHaveLeadingSpace = commentGroup
+ .map(({ value }) => value)
+ .filter(line => line.trim().length)
+ .every(line => line.startsWith(" "));
+
+ return commentGroup.map(({ value }) => (allLinesHaveLeadingSpace ? value.replace(/^ /u, "") : value));
+ }
+
+ /**
+ * Processes a comment group that is currently in starred-block form, calculating the offset for each line.
+ * @param {Token} comment A single block comment token in starred-block form.
+ * @returns {string[]} An array of the processed lines.
+ */
+ function processStarredBlockComment(comment) {
+ const lines = comment.value.split(astUtils.LINEBREAK_MATCHER)
+ .filter((line, i, linesArr) => !(i === 0 || i === linesArr.length - 1))
+ .map(line => line.replace(/^\s*$/u, ""));
+ const allLinesHaveLeadingSpace = lines
+ .map(line => line.replace(/\s*\*/u, ""))
+ .filter(line => line.trim().length)
+ .every(line => line.startsWith(" "));
+
+ return lines.map(line => line.replace(allLinesHaveLeadingSpace ? /\s*\* ?/u : /\s*\*/u, ""));
+ }
+
+ /**
+ * Processes a comment group that is currently in bare-block form, calculating the offset for each line.
+ * @param {Token} comment A single block comment token in bare-block form.
+ * @returns {string[]} An array of the processed lines.
+ */
+ function processBareBlockComment(comment) {
+ const lines = comment.value.split(astUtils.LINEBREAK_MATCHER).map(line => line.replace(/^\s*$/u, ""));
+ const leadingWhitespace = `${sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0])} `;
+ let offset = "";
+
+ /*
+ * Calculate the offset of the least indented line and use that as the basis for offsetting all the lines.
+ * The first line should not be checked because it is inline with the opening block comment delimiter.
+ */
+ for (const [i, line] of lines.entries()) {
+ if (!line.trim().length || i === 0) {
+ continue;
+ }
+
+ const [, lineOffset] = line.match(/^(\s*\*?\s*)/u);
+
+ if (lineOffset.length < leadingWhitespace.length) {
+ const newOffset = leadingWhitespace.slice(lineOffset.length - leadingWhitespace.length);
+
+ if (newOffset.length > offset.length) {
+ offset = newOffset;
+ }
+ }
+ }
+
+ return lines.map(line => {
+ const match = line.match(/^(\s*\*?\s*)(.*)/u);
+ const [, lineOffset, lineContents] = match;
+
+ if (lineOffset.length > leadingWhitespace.length) {
+ return `${lineOffset.slice(leadingWhitespace.length - (offset.length + lineOffset.length))}${lineContents}`;
+ }
+
+ if (lineOffset.length < leadingWhitespace.length) {
+ return `${lineOffset.slice(leadingWhitespace.length)}${lineContents}`;
+ }
+
+ return lineContents;
+ });
+ }
+
+ /**
+ * Gets a list of comment lines in a group, formatting leading whitespace as necessary.
+ * @param {Token[]} commentGroup A group of comments containing either multiple line comments or a single block comment.
+ * @returns {string[]} A list of comment lines.
*/
function getCommentLines(commentGroup) {
- if (commentGroup[0].type === "Line") {
- return commentGroup.map(comment => comment.value);
+ const [firstComment] = commentGroup;
+
+ if (firstComment.type === "Line") {
+ return processSeparateLineComments(commentGroup);
}
- return commentGroup[0].value
- .split(astUtils.LINEBREAK_MATCHER)
- .map(line => line.replace(/^\s*\*?/u, ""));
+
+ if (isStarredBlockComment(commentGroup)) {
+ return processStarredBlockComment(firstComment);
+ }
+
+ return processBareBlockComment(firstComment);
+ }
+
+ /**
+ * Gets the initial offset (whitespace) from the beginning of a line to a given comment token.
+ * @param {Token} comment The token to check.
+ * @returns {string} The offset from the beginning of a line to the token.
+ */
+ function getInitialOffset(comment) {
+ return sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0]);
}
/**
@@ -63,10 +196,9 @@ module.exports = {
* @returns {string} A representation of the comment value in starred-block form, excluding start and end markers
*/
function convertToStarredBlock(firstComment, commentLinesList) {
- const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
- const starredLines = commentLinesList.map(line => `${initialOffset} *${line}`);
+ const initialOffset = getInitialOffset(firstComment);
- return `\n${starredLines.join("\n")}\n${initialOffset} `;
+ return `/*\n${commentLinesList.map(line => `${initialOffset} * ${line}`).join("\n")}\n${initialOffset} */`;
}
/**
@@ -76,10 +208,7 @@ module.exports = {
* @returns {string} A representation of the comment value in separate-line form
*/
function convertToSeparateLines(firstComment, commentLinesList) {
- const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
- const separateLines = commentLinesList.map(line => `// ${line.trim()}`);
-
- return separateLines.join(`\n${initialOffset}`);
+ return commentLinesList.map(line => `// ${line}`).join(`\n${getInitialOffset(firstComment)}`);
}
/**
@@ -89,24 +218,7 @@ module.exports = {
* @returns {string} A representation of the comment value in bare-block form
*/
function convertToBlock(firstComment, commentLinesList) {
- const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
- const blockLines = commentLinesList.map(line => line.trim());
-
- return `/* ${blockLines.join(`\n${initialOffset} `)} */`;
- }
-
- /**
- * Check a comment is JSDoc form
- * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment
- * @returns {boolean} if commentGroup is JSDoc form, return true
- */
- function isJSDoc(commentGroup) {
- const lines = commentGroup[0].value.split(astUtils.LINEBREAK_MATCHER);
-
- return commentGroup[0].type === "Block" &&
- /^\*\s*$/u.test(lines[0]) &&
- lines.slice(1, -1).every(line => /^\s* /u.test(line)) &&
- /^\s*$/u.test(lines[lines.length - 1]);
+ return `/* ${commentLinesList.join(`\n${getInitialOffset(firstComment)} `)} */`;
}
/**
@@ -117,6 +229,7 @@ module.exports = {
*/
const commentGroupCheckers = {
"starred-block"(commentGroup) {
+ const [firstComment] = commentGroup;
const commentLines = getCommentLines(commentGroup);
if (commentLines.some(value => value.includes("*/"))) {
@@ -126,31 +239,30 @@ module.exports = {
if (commentGroup.length > 1) {
context.report({
loc: {
- start: commentGroup[0].loc.start,
+ start: firstComment.loc.start,
end: commentGroup[commentGroup.length - 1].loc.end
},
messageId: "expectedBlock",
fix(fixer) {
- const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
- const starredBlock = `/*${convertToStarredBlock(commentGroup[0], commentLines)}*/`;
+ const range = [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]];
return commentLines.some(value => value.startsWith("/"))
? null
- : fixer.replaceTextRange(range, starredBlock);
+ : fixer.replaceTextRange(range, convertToStarredBlock(firstComment, commentLines));
}
});
} else {
- const block = commentGroup[0];
- const lines = block.value.split(astUtils.LINEBREAK_MATCHER);
- const expectedLinePrefix = `${sourceCode.text.slice(block.range[0] - block.loc.start.column, block.range[0])} *`;
+ const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER);
+ const expectedLeadingWhitespace = getInitialOffset(firstComment);
+ const expectedLinePrefix = `${expectedLeadingWhitespace} *`;
if (!/^\*?\s*$/u.test(lines[0])) {
- const start = block.value.startsWith("*") ? block.range[0] + 1 : block.range[0];
+ const start = firstComment.value.startsWith("*") ? firstComment.range[0] + 1 : firstComment.range[0];
context.report({
loc: {
- start: block.loc.start,
- end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
+ start: firstComment.loc.start,
+ end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 }
},
messageId: "startNewline",
fix: fixer => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`)
@@ -160,36 +272,54 @@ module.exports = {
if (!/^\s*$/u.test(lines[lines.length - 1])) {
context.report({
loc: {
- start: { line: block.loc.end.line, column: block.loc.end.column - 2 },
- end: block.loc.end
+ start: { line: firstComment.loc.end.line, column: firstComment.loc.end.column - 2 },
+ end: firstComment.loc.end
},
messageId: "endNewline",
- fix: fixer => fixer.replaceTextRange([block.range[1] - 2, block.range[1]], `\n${expectedLinePrefix}/`)
+ fix: fixer => fixer.replaceTextRange([firstComment.range[1] - 2, firstComment.range[1]], `\n${expectedLinePrefix}/`)
});
}
- for (let lineNumber = block.loc.start.line + 1; lineNumber <= block.loc.end.line; lineNumber++) {
+ for (let lineNumber = firstComment.loc.start.line + 1; lineNumber <= firstComment.loc.end.line; lineNumber++) {
const lineText = sourceCode.lines[lineNumber - 1];
+ const errorType = isStarredCommentLine(lineText)
+ ? "alignment"
+ : "missingStar";
if (!lineText.startsWith(expectedLinePrefix)) {
context.report({
loc: {
start: { line: lineNumber, column: 0 },
- end: { line: lineNumber, column: sourceCode.lines[lineNumber - 1].length }
+ end: { line: lineNumber, column: lineText.length }
},
- messageId: /^\s*\*/u.test(lineText)
- ? "alignment"
- : "missingStar",
+ messageId: errorType,
fix(fixer) {
const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 });
- const linePrefixLength = lineText.match(/^\s*\*? ?/u)[0].length;
- const commentStartIndex = lineStartIndex + linePrefixLength;
- const replacementText = lineNumber === block.loc.end.line || lineText.length === linePrefixLength
- ? expectedLinePrefix
- : `${expectedLinePrefix} `;
+ if (errorType === "alignment") {
+ const [, commentTextPrefix = ""] = lineText.match(/^(\s*\*)/u) || [];
+ const commentTextStartIndex = lineStartIndex + commentTextPrefix.length;
+
+ return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], expectedLinePrefix);
+ }
+
+ const [, commentTextPrefix = ""] = lineText.match(/^(\s*)/u) || [];
+ const commentTextStartIndex = lineStartIndex + commentTextPrefix.length;
+ let offset;
+
+ for (const [idx, line] of lines.entries()) {
+ if (!/\S+/u.test(line)) {
+ continue;
+ }
+
+ const lineTextToAlignWith = sourceCode.lines[firstComment.loc.start.line - 1 + idx];
+ const [, prefix = "", initialOffset = ""] = lineTextToAlignWith.match(/^(\s*(?:\/?\*)?(\s*))/u) || [];
- return fixer.replaceTextRange([lineStartIndex, commentStartIndex], replacementText);
+ offset = `${commentTextPrefix.slice(prefix.length)}${initialOffset}`;
+ break;
+ }
+
+ return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], `${expectedLinePrefix}${offset}`);
}
});
}
@@ -197,67 +327,68 @@ module.exports = {
}
},
"separate-lines"(commentGroup) {
- if (!isJSDoc(commentGroup) && commentGroup[0].type === "Block") {
- const commentLines = getCommentLines(commentGroup);
- const block = commentGroup[0];
- const tokenAfter = sourceCode.getTokenAfter(block, { includeComments: true });
+ const [firstComment] = commentGroup;
+
+ if (firstComment.type !== "Block" || isJSDocComment(commentGroup)) {
+ return;
+ }
- if (tokenAfter && block.loc.end.line === tokenAfter.loc.start.line) {
- return;
+ const commentLines = getCommentLines(commentGroup);
+ const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true });
+
+ if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) {
+ return;
+ }
+
+ context.report({
+ loc: {
+ start: firstComment.loc.start,
+ end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 }
+ },
+ messageId: "expectedLines",
+ fix(fixer) {
+ return fixer.replaceText(firstComment, convertToSeparateLines(firstComment, commentLines));
}
+ });
+ },
+ "bare-block"(commentGroup) {
+ if (isJSDocComment(commentGroup)) {
+ return;
+ }
+ const [firstComment] = commentGroup;
+ const commentLines = getCommentLines(commentGroup);
+
+ // Disallows consecutive line comments in favor of using a block comment.
+ if (firstComment.type === "Line" && commentLines.length > 1 &&
+ !commentLines.some(value => value.includes("*/"))) {
context.report({
loc: {
- start: block.loc.start,
- end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
+ start: firstComment.loc.start,
+ end: commentGroup[commentGroup.length - 1].loc.end
},
- messageId: "expectedLines",
+ messageId: "expectedBlock",
fix(fixer) {
- return fixer.replaceText(block, convertToSeparateLines(block, commentLines.filter(line => line)));
+ return fixer.replaceTextRange(
+ [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]],
+ convertToBlock(firstComment, commentLines)
+ );
}
});
}
- },
- "bare-block"(commentGroup) {
- if (!isJSDoc(commentGroup)) {
- const commentLines = getCommentLines(commentGroup);
-
- // disallows consecutive line comments in favor of using a block comment.
- if (commentGroup[0].type === "Line" && commentLines.length > 1 &&
- !commentLines.some(value => value.includes("*/"))) {
- context.report({
- loc: {
- start: commentGroup[0].loc.start,
- end: commentGroup[commentGroup.length - 1].loc.end
- },
- messageId: "expectedBlock",
- fix(fixer) {
- const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
- const block = convertToBlock(commentGroup[0], commentLines.filter(line => line));
-
- return fixer.replaceTextRange(range, block);
- }
- });
- }
- // prohibits block comments from having a * at the beginning of each line.
- if (commentGroup[0].type === "Block") {
- const block = commentGroup[0];
- const lines = block.value.split(astUtils.LINEBREAK_MATCHER).filter(line => line.trim());
-
- if (lines.length > 0 && lines.every(line => /^\s*\*/u.test(line))) {
- context.report({
- loc: {
- start: block.loc.start,
- end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
- },
- messageId: "expectedBareBlock",
- fix(fixer) {
- return fixer.replaceText(block, convertToBlock(block, commentLines.filter(line => line)));
- }
- });
+ // Prohibits block comments from having a * at the beginning of each line.
+ if (isStarredBlockComment(commentGroup)) {
+ context.report({
+ loc: {
+ start: firstComment.loc.start,
+ end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 }
+ },
+ messageId: "expectedBareBlock",
+ fix(fixer) {
+ return fixer.replaceText(firstComment, convertToBlock(firstComment, commentLines));
}
- }
+ });
}
}
};
diff --git a/tools/node_modules/eslint/lib/rules/no-cond-assign.js b/tools/node_modules/eslint/lib/rules/no-cond-assign.js
index 67dcd28b20..3843a7ac2e 100644
--- a/tools/node_modules/eslint/lib/rules/no-cond-assign.js
+++ b/tools/node_modules/eslint/lib/rules/no-cond-assign.js
@@ -2,10 +2,21 @@
* @fileoverview Rule to flag assignment in a conditional statement's test expression
* @author Stephen Murray <spmurrayzzz>
*/
+
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const astUtils = require("./utils/ast-utils");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const TEST_CONDITION_PARENT_TYPES = new Set(["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ConditionalExpression"]);
+
const NODE_DESCRIPTIONS = {
DoWhileStatement: "a 'do...while' statement",
ForStatement: "a 'for' statement",
@@ -55,7 +66,7 @@ module.exports = {
*/
function isConditionalTestExpression(node) {
return node.parent &&
- node.parent.test &&
+ TEST_CONDITION_PARENT_TYPES.has(node.parent.type) &&
node === node.parent.test;
}
@@ -105,8 +116,7 @@ module.exports = {
) {
context.report({
- node,
- loc: node.test.loc.start,
+ node: node.test,
messageId: "missing"
});
}
@@ -122,7 +132,7 @@ module.exports = {
if (ancestor) {
context.report({
- node: ancestor,
+ node,
messageId: "unexpected",
data: {
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
diff --git a/tools/node_modules/eslint/lib/rules/no-constructor-return.js b/tools/node_modules/eslint/lib/rules/no-constructor-return.js
new file mode 100644
index 0000000000..4757770b7c
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/no-constructor-return.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Rule to disallow returning value from constructor.
+ * @author Pig Fang <https://github.com/g-plane>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow returning value from constructor",
+ category: "Best Practices",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/no-constructor-return"
+ },
+
+ schema: {},
+
+ fixable: null,
+
+ messages: {
+ unexpected: "Unexpected return statement in constructor."
+ }
+ },
+
+ create(context) {
+ const stack = [];
+
+ return {
+ onCodePathStart(_, node) {
+ stack.push(node);
+ },
+ onCodePathEnd() {
+ stack.pop();
+ },
+ ReturnStatement(node) {
+ const last = stack[stack.length - 1];
+
+ if (!last.parent) {
+ return;
+ }
+
+ if (
+ last.parent.type === "MethodDefinition" &&
+ last.parent.kind === "constructor" &&
+ (node.parent.parent === last || node.argument)
+ ) {
+ context.report({
+ node,
+ messageId: "unexpected"
+ });
+ }
+ }
+ };
+ }
+};
diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js b/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js
new file mode 100644
index 0000000000..a165e16607
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js
@@ -0,0 +1,122 @@
+/**
+ * @fileoverview Rule to disallow duplicate conditions in if-else-if chains
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the first given array is a subset of the second given array.
+ * @param {Function} comparator A function to compare two elements, should return `true` if they are equal.
+ * @param {Array} arrA The array to compare from.
+ * @param {Array} arrB The array to compare against.
+ * @returns {boolean} `true` if the array `arrA` is a subset of the array `arrB`.
+ */
+function isSubsetByComparator(comparator, arrA, arrB) {
+ return arrA.every(a => arrB.some(b => comparator(a, b)));
+}
+
+/**
+ * Splits the given node by the given logical operator.
+ * @param {string} operator Logical operator `||` or `&&`.
+ * @param {ASTNode} node The node to split.
+ * @returns {ASTNode[]} Array of conditions that makes the node when joined by the operator.
+ */
+function splitByLogicalOperator(operator, node) {
+ if (node.type === "LogicalExpression" && node.operator === operator) {
+ return [...splitByLogicalOperator(operator, node.left), ...splitByLogicalOperator(operator, node.right)];
+ }
+ return [node];
+}
+
+const splitByOr = splitByLogicalOperator.bind(null, "||");
+const splitByAnd = splitByLogicalOperator.bind(null, "&&");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow duplicate conditions in if-else-if chains",
+ category: "Possible Errors",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/no-dupe-else-if"
+ },
+
+ schema: [],
+
+ messages: {
+ unexpected: "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain."
+ }
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+
+ /**
+ * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes
+ * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators.
+ * @param {ASTNode} a First node.
+ * @param {ASTNode} b Second node.
+ * @returns {boolean} `true` if the nodes are considered to be equal.
+ */
+ function equal(a, b) {
+ if (a.type !== b.type) {
+ return false;
+ }
+
+ if (
+ a.type === "LogicalExpression" &&
+ (a.operator === "||" || a.operator === "&&") &&
+ a.operator === b.operator
+ ) {
+ return equal(a.left, b.left) && equal(a.right, b.right) ||
+ equal(a.left, b.right) && equal(a.right, b.left);
+ }
+
+ return astUtils.equalTokens(a, b, sourceCode);
+ }
+
+ const isSubset = isSubsetByComparator.bind(null, equal);
+
+ return {
+ IfStatement(node) {
+ const test = node.test,
+ conditionsToCheck = test.type === "LogicalExpression" && test.operator === "&&"
+ ? [test, ...splitByAnd(test)]
+ : [test];
+ let current = node,
+ listToCheck = conditionsToCheck.map(c => splitByOr(c).map(splitByAnd));
+
+ while (current.parent && current.parent.type === "IfStatement" && current.parent.alternate === current) {
+ current = current.parent;
+
+ const currentOrOperands = splitByOr(current.test).map(splitByAnd);
+
+ listToCheck = listToCheck.map(orOperands => orOperands.filter(
+ orOperand => !currentOrOperands.some(currentOrOperand => isSubset(currentOrOperand, orOperand))
+ ));
+
+ if (listToCheck.some(orOperands => orOperands.length === 0)) {
+ context.report({ node: test, messageId: "unexpected" });
+ break;
+ }
+ }
+ }
+ };
+ }
+};
diff --git a/tools/node_modules/eslint/lib/rules/no-implicit-globals.js b/tools/node_modules/eslint/lib/rules/no-implicit-globals.js
index 2eea2b2846..d4bfa3af82 100644
--- a/tools/node_modules/eslint/lib/rules/no-implicit-globals.js
+++ b/tools/node_modules/eslint/lib/rules/no-implicit-globals.js
@@ -1,5 +1,5 @@
/**
- * @fileoverview Rule to check for implicit global variables and functions.
+ * @fileoverview Rule to check for implicit global variables, functions and classes.
* @author Joshua Peek
*/
@@ -14,41 +14,123 @@ module.exports = {
type: "suggestion",
docs: {
- description: "disallow variable and `function` declarations in the global scope",
+ description: "disallow declarations in the global scope",
category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-implicit-globals"
},
- schema: []
+ schema: [{
+ type: "object",
+ properties: {
+ lexicalBindings: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }],
+
+ messages: {
+ globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.",
+ globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.",
+ globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.",
+ assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.",
+ redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable."
+ }
},
create(context) {
+
+ const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true;
+
+ /**
+ * Reports the node.
+ * @param {ASTNode} node Node to report.
+ * @param {string} messageId Id of the message to report.
+ * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class.
+ * @returns {void}
+ */
+ function report(node, messageId, kind) {
+ context.report({
+ node,
+ messageId,
+ data: {
+ kind
+ }
+ });
+ }
+
return {
Program() {
const scope = context.getScope();
scope.variables.forEach(variable => {
- if (variable.writeable) {
+
+ // Only ESLint global variables have the `writable` key.
+ const isReadonlyEslintGlobalVariable = variable.writeable === false;
+ const isWritableEslintGlobalVariable = variable.writeable === true;
+
+ if (isWritableEslintGlobalVariable) {
+
+ // Everything is allowed with writable ESLint global variables.
return;
}
variable.defs.forEach(def => {
+ const defNode = def.node;
+
if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
- context.report({ node: def.node, message: "Implicit global variable, assign as global property instead." });
+ if (isReadonlyEslintGlobalVariable) {
+ report(defNode, "redeclarationOfReadonlyGlobal");
+ } else {
+ report(
+ defNode,
+ "globalNonLexicalBinding",
+ def.type === "FunctionName" ? "function" : `'${def.parent.kind}'`
+ );
+ }
+ }
+
+ if (checkLexicalBindings) {
+ if (def.type === "ClassName" ||
+ (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) {
+ if (isReadonlyEslintGlobalVariable) {
+ report(defNode, "redeclarationOfReadonlyGlobal");
+ } else {
+ report(
+ defNode,
+ "globalLexicalBinding",
+ def.type === "ClassName" ? "class" : `'${def.parent.kind}'`
+ );
+ }
+ }
}
});
});
+ // Undeclared assigned variables.
scope.implicit.variables.forEach(variable => {
const scopeVariable = scope.set.get(variable.name);
+ let messageId;
- if (scopeVariable && scopeVariable.writeable) {
- return;
+ if (scopeVariable) {
+
+ // ESLint global variable
+ if (scopeVariable.writeable) {
+ return;
+ }
+ messageId = "assignmentToReadonlyGlobal";
+
+ } else {
+
+ // Reference to an unknown variable, possible global leak.
+ messageId = "globalVariableLeak";
}
+ // def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
variable.defs.forEach(def => {
- context.report({ node: def.node, message: "Implicit global variable, assign as global property instead." });
+ report(def.node, messageId);
});
});
}
diff --git a/tools/node_modules/eslint/lib/rules/no-inline-comments.js b/tools/node_modules/eslint/lib/rules/no-inline-comments.js
index b35db11410..bd226ecc35 100644
--- a/tools/node_modules/eslint/lib/rules/no-inline-comments.js
+++ b/tools/node_modules/eslint/lib/rules/no-inline-comments.js
@@ -35,22 +35,36 @@ module.exports = {
*/
function testCodeAroundComment(node) {
- // Get the whole line and cut it off at the start of the comment
- const startLine = String(sourceCode.lines[node.loc.start.line - 1]);
- const endLine = String(sourceCode.lines[node.loc.end.line - 1]);
+ const startLine = String(sourceCode.lines[node.loc.start.line - 1]),
+ endLine = String(sourceCode.lines[node.loc.end.line - 1]),
+ preamble = startLine.slice(0, node.loc.start.column).trim(),
+ postamble = endLine.slice(node.loc.end.column).trim(),
+ isPreambleEmpty = !preamble,
+ isPostambleEmpty = !postamble;
- const preamble = startLine.slice(0, node.loc.start.column).trim();
+ // Nothing on both sides
+ if (isPreambleEmpty && isPostambleEmpty) {
+ return;
+ }
- // Also check after the comment
- const postamble = endLine.slice(node.loc.end.column).trim();
+ // JSX Exception
+ if (
+ (isPreambleEmpty || preamble === "{") &&
+ (isPostambleEmpty || postamble === "}")
+ ) {
+ const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]);
- // Check that this comment isn't an ESLint directive
- const isDirective = astUtils.isDirectiveComment(node);
+ if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") {
+ return;
+ }
+ }
- // Should be empty if there was only whitespace around the comment
- if (!isDirective && (preamble || postamble)) {
- context.report({ node, message: "Unexpected comment inline with code." });
+ // Don't report ESLint directive comments
+ if (astUtils.isDirectiveComment(node)) {
+ return;
}
+
+ context.report({ node, message: "Unexpected comment inline with code." });
}
//--------------------------------------------------------------------------
diff --git a/tools/node_modules/eslint/lib/rules/no-invalid-this.js b/tools/node_modules/eslint/lib/rules/no-invalid-this.js
index b1dddd9319..5398fc3b5f 100644
--- a/tools/node_modules/eslint/lib/rules/no-invalid-this.js
+++ b/tools/node_modules/eslint/lib/rules/no-invalid-this.js
@@ -26,10 +26,23 @@ module.exports = {
url: "https://eslint.org/docs/rules/no-invalid-this"
},
- schema: []
+ schema: [
+ {
+ type: "object",
+ properties: {
+ capIsConstructor: {
+ type: "boolean",
+ default: true
+ }
+ },
+ additionalProperties: false
+ }
+ ]
},
create(context) {
+ const options = context.options[0] || {};
+ const capIsConstructor = options.capIsConstructor !== false;
const stack = [],
sourceCode = context.getSourceCode();
@@ -48,7 +61,8 @@ module.exports = {
current.init = true;
current.valid = !astUtils.isDefaultThisBinding(
current.node,
- sourceCode
+ sourceCode,
+ { capIsConstructor }
);
}
return current;
diff --git a/tools/node_modules/eslint/lib/rules/no-octal-escape.js b/tools/node_modules/eslint/lib/rules/no-octal-escape.js
index 7f6845ec70..5b4c7b2ebb 100644
--- a/tools/node_modules/eslint/lib/rules/no-octal-escape.js
+++ b/tools/node_modules/eslint/lib/rules/no-octal-escape.js
@@ -38,7 +38,7 @@ module.exports = {
// \0 represents a valid NULL character if it isn't followed by a digit.
const match = node.raw.match(
- /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|[1-7])/u
+ /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|0(?=[89])|[1-7])/su
);
if (match) {
diff --git a/tools/node_modules/eslint/lib/rules/no-setter-return.js b/tools/node_modules/eslint/lib/rules/no-setter-return.js
new file mode 100644
index 0000000000..e0948696c3
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/no-setter-return.js
@@ -0,0 +1,227 @@
+/**
+ * @fileoverview Rule to disallow returning values from setters
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { findVariable } = require("eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the given identifier node is a reference to a global variable.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} True if the identifier is a reference to a global variable.
+ */
+function isGlobalReference(node, scope) {
+ const variable = findVariable(scope, node);
+
+ return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+}
+
+/**
+ * Determines whether the given node is an argument of the specified global method call, at the given `index` position.
+ * E.g., for given `index === 1`, this function checks for `objectName.methodName(foo, node)`, where objectName is a global variable.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @param {string} objectName Name of the global object.
+ * @param {string} methodName Name of the method.
+ * @param {number} index The given position.
+ * @returns {boolean} `true` if the node is argument at the given position.
+ */
+function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
+ const parent = node.parent;
+
+ return parent.type === "CallExpression" &&
+ parent.arguments[index] === node &&
+ parent.callee.type === "MemberExpression" &&
+ astUtils.getStaticPropertyName(parent.callee) === methodName &&
+ parent.callee.object.type === "Identifier" &&
+ parent.callee.object.name === objectName &&
+ isGlobalReference(parent.callee.object, scope);
+}
+
+/**
+ * Determines whether the given node is used as a property descriptor.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} `true` if the node is a property descriptor.
+ */
+function isPropertyDescriptor(node, scope) {
+ if (
+ isArgumentOfGlobalMethodCall(node, scope, "Object", "defineProperty", 2) ||
+ isArgumentOfGlobalMethodCall(node, scope, "Reflect", "defineProperty", 2)
+ ) {
+ return true;
+ }
+
+ const parent = node.parent;
+
+ if (
+ parent.type === "Property" &&
+ parent.value === node
+ ) {
+ const grandparent = parent.parent;
+
+ if (
+ grandparent.type === "ObjectExpression" &&
+ (
+ isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "create", 1) ||
+ isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "defineProperties", 1)
+ )
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Determines whether the given function node is used as a setter function.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} `true` if the node is a setter.
+ */
+function isSetter(node, scope) {
+ const parent = node.parent;
+
+ if (
+ parent.kind === "set" &&
+ parent.value === node
+ ) {
+
+ // Setter in an object literal or in a class
+ return true;
+ }
+
+ if (
+ parent.type === "Property" &&
+ parent.value === node &&
+ astUtils.getStaticPropertyName(parent) === "set" &&
+ parent.parent.type === "ObjectExpression" &&
+ isPropertyDescriptor(parent.parent, scope)
+ ) {
+
+ // Setter in a property descriptor
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Finds function's outer scope.
+ * @param {Scope} scope Function's own scope.
+ * @returns {Scope} Function's outer scope.
+ */
+function getOuterScope(scope) {
+ const upper = scope.upper;
+
+ if (upper.type === "function-expression-name") {
+ return upper.upper;
+ }
+
+ return upper;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow returning values from setters",
+ category: "Possible Errors",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/no-setter-return"
+ },
+
+ schema: [],
+
+ messages: {
+ returnsValue: "Setter cannot return a value."
+ }
+ },
+
+ create(context) {
+ let funcInfo = null;
+
+ /**
+ * Creates and pushes to the stack a function info object for the given function node.
+ * @param {ASTNode} node The function node.
+ * @returns {void}
+ */
+ function enterFunction(node) {
+ const outerScope = getOuterScope(context.getScope());
+
+ funcInfo = {
+ upper: funcInfo,
+ isSetter: isSetter(node, outerScope)
+ };
+ }
+
+ /**
+ * Pops the current function info object from the stack.
+ * @returns {void}
+ */
+ function exitFunction() {
+ funcInfo = funcInfo.upper;
+ }
+
+ /**
+ * Reports the given node.
+ * @param {ASTNode} node Node to report.
+ * @returns {void}
+ */
+ function report(node) {
+ context.report({ node, messageId: "returnsValue" });
+ }
+
+ return {
+
+ /*
+ * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid
+ * false positives, because a ReturnStatement node can belong to a function declaration inside a setter.
+ *
+ * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor,
+ * but that's out of scope for this rule.
+ */
+ FunctionDeclaration: enterFunction,
+ FunctionExpression: enterFunction,
+ ArrowFunctionExpression(node) {
+ enterFunction(node);
+
+ if (funcInfo.isSetter && node.expression) {
+
+ // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement.
+ report(node.body);
+ }
+ },
+
+ "FunctionDeclaration:exit": exitFunction,
+ "FunctionExpression:exit": exitFunction,
+ "ArrowFunctionExpression:exit": exitFunction,
+
+ ReturnStatement(node) {
+
+ // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`.
+ if (funcInfo && funcInfo.isSetter && node.argument) {
+ report(node);
+ }
+ }
+ };
+ }
+};
diff --git a/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js b/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js
index 3f59815b57..e910e2739a 100644
--- a/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js
+++ b/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js
@@ -38,6 +38,10 @@ module.exports = {
type: "boolean",
default: false
},
+ allowAfterThisConstructor: {
+ type: "boolean",
+ default: false
+ },
enforceInMethodNames: {
type: "boolean",
default: false
@@ -54,6 +58,7 @@ module.exports = {
const ALLOWED_VARIABLES = options.allow ? options.allow : [];
const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
+ const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
//-------------------------------------------------------------------------
@@ -72,7 +77,7 @@ module.exports = {
/**
* Check if identifier has a underscore at the end
- * @param {ASTNode} identifier node to evaluate
+ * @param {string} identifier name of the node
* @returns {boolean} true if its is present
* @private
*/
@@ -84,7 +89,7 @@ module.exports = {
/**
* Check if identifier is a special case member expression
- * @param {ASTNode} identifier node to evaluate
+ * @param {string} identifier name of the node
* @returns {boolean} true if its is a special case
* @private
*/
@@ -94,7 +99,7 @@ module.exports = {
/**
* Check if identifier is a special case variable expression
- * @param {ASTNode} identifier node to evaluate
+ * @param {string} identifier name of the node
* @returns {boolean} true if its is a special case
* @private
*/
@@ -105,6 +110,18 @@ module.exports = {
}
/**
+ * Check if a node is a member reference of this.constructor
+ * @param {ASTNode} node node to evaluate
+ * @returns {boolean} true if it is a reference on this.constructor
+ * @private
+ */
+ function isThisConstructorReference(node) {
+ return node.object.type === "MemberExpression" &&
+ node.object.property.name === "constructor" &&
+ node.object.object.type === "ThisExpression";
+ }
+
+ /**
* Check if function has a underscore at the end
* @param {ASTNode} node node to evaluate
* @returns {void}
@@ -156,11 +173,13 @@ module.exports = {
function checkForTrailingUnderscoreInMemberExpression(node) {
const identifier = node.property.name,
isMemberOfThis = node.object.type === "ThisExpression",
- isMemberOfSuper = node.object.type === "Super";
+ isMemberOfSuper = node.object.type === "Super",
+ isMemberOfThisConstructor = isThisConstructorReference(node);
if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
!(isMemberOfThis && allowAfterThis) &&
!(isMemberOfSuper && allowAfterSuper) &&
+ !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
!isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
context.report({
node,
diff --git a/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js b/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js
index 95527832b2..b5e53174e4 100644
--- a/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js
+++ b/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js
@@ -8,6 +8,7 @@
// Requirements
//------------------------------------------------------------------------------
+const lodash = require("lodash");
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -21,57 +22,83 @@ module.exports = {
type: "suggestion",
docs: {
- description: "disallow unnecessary computed property keys in object literals",
+ description: "disallow unnecessary computed property keys in objects and classes",
category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-computed-key"
},
- schema: [],
+ schema: [{
+ type: "object",
+ properties: {
+ enforceForClassMembers: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }],
fixable: "code"
},
create(context) {
const sourceCode = context.getSourceCode();
+ const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers;
+
+ /**
+ * Reports a given node if it violated this rule.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function check(node) {
+ if (!node.computed) {
+ return;
+ }
- return {
- Property(node) {
- if (!node.computed) {
- return;
- }
-
- const key = node.key,
- nodeType = typeof key.value;
+ const key = node.key,
+ nodeType = typeof key.value;
- if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== "__proto__") {
- context.report({
- node,
- message: MESSAGE_UNNECESSARY_COMPUTED,
- data: { property: sourceCode.getText(key) },
- fix(fixer) {
- const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken);
- const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
- const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
+ let allowedKey;
- if (tokensBetween.slice(0, -1).some((token, index) =>
- sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
+ if (node.type === "MethodDefinition") {
+ allowedKey = node.static ? "prototype" : "constructor";
+ } else {
+ allowedKey = "__proto__";
+ }
- // If there are comments between the brackets and the property name, don't do a fix.
- return null;
- }
+ if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== allowedKey) {
+ context.report({
+ node,
+ message: MESSAGE_UNNECESSARY_COMPUTED,
+ data: { property: sourceCode.getText(key) },
+ fix(fixer) {
+ const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken);
+ const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
+ 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;
+ }
- const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
+ const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
- // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
- const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
- !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
+ // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
+ const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
+ !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
- const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
+ const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
- return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
- }
- });
- }
+ return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
+ }
+ });
}
+ }
+
+ return {
+ Property: check,
+ MethodDefinition: enforceForClassMembers ? check : lodash.noop
};
}
};
diff --git a/tools/node_modules/eslint/lib/rules/no-useless-escape.js b/tools/node_modules/eslint/lib/rules/no-useless-escape.js
index ebfe4cba38..8057e44dda 100644
--- a/tools/node_modules/eslint/lib/rules/no-useless-escape.js
+++ b/tools/node_modules/eslint/lib/rules/no-useless-escape.js
@@ -85,7 +85,14 @@ module.exports = {
description: "disallow unnecessary escape characters",
category: "Best Practices",
recommended: true,
- url: "https://eslint.org/docs/rules/no-useless-escape"
+ url: "https://eslint.org/docs/rules/no-useless-escape",
+ suggestion: true
+ },
+
+ messages: {
+ unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
+ removeEscape: "Remove the `\\`. This maintains the current functionality.",
+ escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
},
schema: []
@@ -103,6 +110,8 @@ module.exports = {
*/
function report(node, startOffset, character) {
const start = sourceCode.getLocFromIndex(sourceCode.getIndexFromLoc(node.loc.start) + startOffset);
+ const rangeStart = sourceCode.getIndexFromLoc(node.loc.start) + startOffset;
+ const range = [rangeStart, rangeStart + 1];
context.report({
node,
@@ -110,8 +119,22 @@ module.exports = {
start,
end: { line: start.line, column: start.column + 1 }
},
- message: "Unnecessary escape character: \\{{character}}.",
- data: { character }
+ messageId: "unnecessaryEscape",
+ data: { character },
+ suggest: [
+ {
+ messageId: "removeEscape",
+ fix(fixer) {
+ return fixer.removeRange(range);
+ }
+ },
+ {
+ messageId: "escapeBackslash",
+ fix(fixer) {
+ return fixer.insertTextBeforeRange(range, "\\");
+ }
+ }
+ ]
});
}
diff --git a/tools/node_modules/eslint/lib/rules/object-curly-spacing.js b/tools/node_modules/eslint/lib/rules/object-curly-spacing.js
index 53ddaaa24a..117a7a3542 100644
--- a/tools/node_modules/eslint/lib/rules/object-curly-spacing.js
+++ b/tools/node_modules/eslint/lib/rules/object-curly-spacing.js
@@ -74,16 +74,16 @@ module.exports = {
* @returns {void}
*/
function reportNoBeginningSpace(node, token) {
+ const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true });
+
context.report({
node,
- loc: token.loc.start,
+ loc: { start: token.loc.end, end: nextToken.loc.start },
message: "There should be no space after '{{token}}'.",
data: {
token: token.value
},
fix(fixer) {
- const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true });
-
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
@@ -96,16 +96,16 @@ module.exports = {
* @returns {void}
*/
function reportNoEndingSpace(node, token) {
+ const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true });
+
context.report({
node,
- loc: token.loc.start,
+ loc: { start: previousToken.loc.end, end: token.loc.start },
message: "There should be no space before '{{token}}'.",
data: {
token: token.value
},
fix(fixer) {
- const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true });
-
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
});
@@ -120,7 +120,7 @@ module.exports = {
function reportRequiredBeginningSpace(node, token) {
context.report({
node,
- loc: token.loc.start,
+ loc: token.loc,
message: "A space is required after '{{token}}'.",
data: {
token: token.value
@@ -140,7 +140,7 @@ module.exports = {
function reportRequiredEndingSpace(node, token) {
context.report({
node,
- loc: token.loc.start,
+ loc: token.loc,
message: "A space is required before '{{token}}'.",
data: {
token: token.value
diff --git a/tools/node_modules/eslint/lib/rules/operator-assignment.js b/tools/node_modules/eslint/lib/rules/operator-assignment.js
index e294668b42..b19ba0d02e 100644
--- a/tools/node_modules/eslint/lib/rules/operator-assignment.js
+++ b/tools/node_modules/eslint/lib/rules/operator-assignment.js
@@ -71,6 +71,9 @@ function same(a, b) {
*/
return same(a.object, b.object) && same(a.property, b.property);
+ case "ThisExpression":
+ return true;
+
default:
return false;
}
@@ -83,8 +86,14 @@ function same(a, b) {
* @returns {boolean} `true` if the node can be fixed
*/
function canBeFixed(node) {
- return node.type === "Identifier" ||
- node.type === "MemberExpression" && node.object.type === "Identifier" && (!node.computed || node.property.type === "Literal");
+ return (
+ node.type === "Identifier" ||
+ (
+ node.type === "MemberExpression" &&
+ (node.object.type === "Identifier" || node.object.type === "ThisExpression") &&
+ (!node.computed || node.property.type === "Literal")
+ )
+ );
}
module.exports = {
diff --git a/tools/node_modules/eslint/lib/rules/prefer-const.js b/tools/node_modules/eslint/lib/rules/prefer-const.js
index 87f8389212..439a4db3c9 100644
--- a/tools/node_modules/eslint/lib/rules/prefer-const.js
+++ b/tools/node_modules/eslint/lib/rules/prefer-const.js
@@ -356,7 +356,9 @@ module.exports = {
const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
const variables = [];
let reportCount = 0;
- let name = "";
+ let checkedId = null;
+ let checkedName = "";
+
/**
* Reports given identifier nodes if all of the nodes should be declared
@@ -387,25 +389,30 @@ module.exports = {
/*
* First we check the declaration type and then depending on
* if the type is a "VariableDeclarator" or its an "ObjectPattern"
- * we compare the name from the first identifier, if the names are different
- * we assign the new name and reset the count of reportCount and nodeCount in
+ * we compare the name and id from the first identifier, if the names are different
+ * we assign the new name, id and reset the count of reportCount and nodeCount in
* order to check each block for the number of reported errors and base our fix
* based on comparing nodes.length and nodesToReport.length.
*/
if (firstDecParent.type === "VariableDeclarator") {
- if (firstDecParent.id.name !== name) {
- name = firstDecParent.id.name;
+ if (firstDecParent.id.name !== checkedName) {
+ checkedName = firstDecParent.id.name;
reportCount = 0;
}
if (firstDecParent.id.type === "ObjectPattern") {
- if (firstDecParent.init.name !== name) {
- name = firstDecParent.init.name;
+ if (firstDecParent.init.name !== checkedName) {
+ checkedName = firstDecParent.init.name;
reportCount = 0;
}
}
+
+ if (firstDecParent.id !== checkedId) {
+ checkedId = firstDecParent.id;
+ reportCount = 0;
+ }
}
}
}
diff --git a/tools/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js b/tools/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js
new file mode 100644
index 0000000000..5e75ef4724
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js
@@ -0,0 +1,189 @@
+/**
+ * @fileoverview Rule to disallow Math.pow in favor of the ** operator
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { CALL, ReferenceTracker } = require("eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" });
+
+/**
+ * Determines whether the given node needs parens if used as the base in an exponentiation binary expression.
+ * @param {ASTNode} base The node to check.
+ * @returns {boolean} `true` if the node needs to be parenthesised.
+ */
+function doesBaseNeedParens(base) {
+ return (
+
+ // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c
+ astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR ||
+
+ // An unary operator cannot be used immediately before an exponentiation expression
+ base.type === "UnaryExpression"
+ );
+}
+
+/**
+ * Determines whether the given node needs parens if used as the exponent in an exponentiation binary expression.
+ * @param {ASTNode} exponent The node to check.
+ * @returns {boolean} `true` if the node needs to be parenthesised.
+ */
+function doesExponentNeedParens(exponent) {
+
+ // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c
+ return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR;
+}
+
+/**
+ * Determines whether an exponentiation binary expression at the place of the given node would need parens.
+ * @param {ASTNode} node A node that would be replaced by an exponentiation binary expression.
+ * @param {SourceCode} sourceCode A SourceCode object.
+ * @returns {boolean} `true` if the expression needs to be parenthesised.
+ */
+function doesExponentiationExpressionNeedParens(node, sourceCode) {
+ const parent = node.parent;
+
+ const needsParens = (
+ parent.type === "ClassDeclaration" ||
+ (
+ parent.type.endsWith("Expression") &&
+ astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR &&
+ !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&
+ !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&
+ !(parent.type === "MemberExpression" && parent.computed && parent.property === node) &&
+ !(parent.type === "ArrayExpression")
+ )
+ );
+
+ return needsParens && !astUtils.isParenthesised(sourceCode, node);
+}
+
+/**
+ * Optionally parenthesizes given text.
+ * @param {string} text The text to parenthesize.
+ * @param {boolean} shouldParenthesize If `true`, the text will be parenthesised.
+ * @returns {string} parenthesised or unchanged text.
+ */
+function parenthesizeIfShould(text, shouldParenthesize) {
+ return shouldParenthesize ? `(${text})` : text;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "disallow the use of `Math.pow` in favor of the `**` operator",
+ category: "Stylistic Issues",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/prefer-exponentiation-operator"
+ },
+
+ schema: [],
+ fixable: "code",
+
+ messages: {
+ useExponentiation: "Use the '**' operator instead of 'Math.pow'."
+ }
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+
+ /**
+ * Reports the given node.
+ * @param {ASTNode} node 'Math.pow()' node to report.
+ * @returns {void}
+ */
+ function report(node) {
+ context.report({
+ node,
+ messageId: "useExponentiation",
+ fix(fixer) {
+ if (
+ node.arguments.length !== 2 ||
+ node.arguments.some(arg => arg.type === "SpreadElement") ||
+ sourceCode.getCommentsInside(node).length > 0
+ ) {
+ return null;
+ }
+
+ const base = node.arguments[0],
+ exponent = node.arguments[1],
+ baseText = sourceCode.getText(base),
+ exponentText = sourceCode.getText(exponent),
+ shouldParenthesizeBase = doesBaseNeedParens(base),
+ shouldParenthesizeExponent = doesExponentNeedParens(exponent),
+ shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode);
+
+ let prefix = "",
+ suffix = "";
+
+ if (!shouldParenthesizeAll) {
+ if (!shouldParenthesizeBase) {
+ const firstReplacementToken = sourceCode.getFirstToken(base),
+ tokenBefore = sourceCode.getTokenBefore(node);
+
+ if (
+ tokenBefore &&
+ tokenBefore.range[1] === node.range[0] &&
+ !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)
+ ) {
+ prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c
+ }
+ }
+ if (!shouldParenthesizeExponent) {
+ const lastReplacementToken = sourceCode.getLastToken(exponent),
+ tokenAfter = sourceCode.getTokenAfter(node);
+
+ if (
+ tokenAfter &&
+ node.range[1] === tokenAfter.range[0] &&
+ !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter)
+ ) {
+ suffix = " "; // Math.pow(a, b)in c -> a**b in c
+ }
+ }
+ }
+
+ const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase),
+ exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent),
+ replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll);
+
+ return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);
+ }
+ });
+ }
+
+ return {
+ Program() {
+ const scope = context.getScope();
+ const tracker = new ReferenceTracker(scope);
+ const trackMap = {
+ Math: {
+ pow: { [CALL]: true }
+ }
+ };
+
+ for (const { node } of tracker.iterateGlobalReferences(trackMap)) {
+ report(node);
+ }
+ }
+ };
+ }
+};
diff --git a/tools/node_modules/eslint/lib/rules/require-await.js b/tools/node_modules/eslint/lib/rules/require-await.js
index 0aa6fce7e1..22c111b6dc 100644
--- a/tools/node_modules/eslint/lib/rules/require-await.js
+++ b/tools/node_modules/eslint/lib/rules/require-await.js
@@ -89,9 +89,17 @@ module.exports = {
"ArrowFunctionExpression:exit": exitFunction,
AwaitExpression() {
+ if (!scopeInfo) {
+ return;
+ }
+
scopeInfo.hasAwait = true;
},
ForOfStatement(node) {
+ if (!scopeInfo) {
+ return;
+ }
+
if (node.await) {
scopeInfo.hasAwait = true;
}
diff --git a/tools/node_modules/eslint/lib/rules/semi.js b/tools/node_modules/eslint/lib/rules/semi.js
index a159e19d34..22e299efe7 100644
--- a/tools/node_modules/eslint/lib/rules/semi.js
+++ b/tools/node_modules/eslint/lib/rules/semi.js
@@ -93,17 +93,20 @@ module.exports = {
const lastToken = sourceCode.getLastToken(node);
let message,
fix,
- loc = lastToken.loc;
+ loc;
if (!missing) {
message = "Missing semicolon.";
- loc = loc.end;
+ loc = {
+ start: lastToken.loc.end,
+ end: astUtils.getNextLocation(sourceCode, lastToken.loc.end)
+ };
fix = function(fixer) {
return fixer.insertTextAfter(lastToken, ";");
};
} else {
message = "Extra semicolon.";
- loc = loc.start;
+ loc = lastToken.loc;
fix = function(fixer) {
/*
diff --git a/tools/node_modules/eslint/lib/rules/space-infix-ops.js b/tools/node_modules/eslint/lib/rules/space-infix-ops.js
index b2fbe47b47..bd2c0ae0e1 100644
--- a/tools/node_modules/eslint/lib/rules/space-infix-ops.js
+++ b/tools/node_modules/eslint/lib/rules/space-infix-ops.js
@@ -69,7 +69,7 @@ module.exports = {
function report(mainNode, culpritToken) {
context.report({
node: mainNode,
- loc: culpritToken.loc.start,
+ loc: culpritToken.loc,
message: "Operator '{{operator}}' must be spaced.",
data: {
operator: culpritToken.value
diff --git a/tools/node_modules/eslint/lib/rules/spaced-comment.js b/tools/node_modules/eslint/lib/rules/spaced-comment.js
index 958bb2c644..daf56cd6bb 100644
--- a/tools/node_modules/eslint/lib/rules/spaced-comment.js
+++ b/tools/node_modules/eslint/lib/rules/spaced-comment.js
@@ -249,7 +249,8 @@ module.exports = {
beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers),
endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"),
hasExceptions: exceptions.length > 0,
- markers: new RegExp(`^(${markers.map(escape).join("|")})`, "u")
+ captureMarker: new RegExp(`^(${markers.map(escape).join("|")})`, "u"),
+ markers: new Set(markers)
};
return rule;
@@ -322,8 +323,8 @@ module.exports = {
rule = styleRules[type],
commentIdentifier = type === "block" ? "/*" : "//";
- // Ignores empty comments.
- if (node.value.length === 0) {
+ // Ignores empty comments and comments that consist only of a marker.
+ if (node.value.length === 0 || rule.markers.has(node.value)) {
return;
}
@@ -333,7 +334,7 @@ module.exports = {
// Checks.
if (requireSpace) {
if (!beginMatch) {
- const hasMarker = rule.markers.exec(node.value);
+ const hasMarker = rule.captureMarker.exec(node.value);
const marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier;
if (rule.hasExceptions) {
diff --git a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js
index 17e056c240..01c6b47b82 100644
--- a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js
+++ b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js
@@ -581,23 +581,31 @@ module.exports = {
*
* First, this checks the node:
*
- * - The function name does not start with uppercase (it's a constructor).
+ * - The function name does not start with uppercase. It's a convention to capitalize the names
+ * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`.
* - The function does not have a JSDoc comment that has a @this tag.
*
* Next, this checks the location of the node.
* If the location is below, this judges `this` is valid.
*
* - The location is not on an object literal.
- * - The location is not assigned to a variable which starts with an uppercase letter.
+ * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous
+ * functions only, as the name of the variable is considered to be the name of the function in this case.
+ * This check is not performed if `capIsConstructor` is set to `false`.
* - The location is not on an ES2015 class.
* - Its `bind`/`call`/`apply` method is not called directly.
* - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given.
* @param {ASTNode} node A function node to check.
* @param {SourceCode} sourceCode A SourceCode instance to get comments.
+ * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts
+ * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors.
* @returns {boolean} The function node is the default `this` binding.
*/
- isDefaultThisBinding(node, sourceCode) {
- if (isES5Constructor(node) || hasJSDocThisTag(node, sourceCode)) {
+ isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) {
+ if (
+ (capIsConstructor && isES5Constructor(node)) ||
+ hasJSDocThisTag(node, sourceCode)
+ ) {
return false;
}
const isAnonymous = node.id === null;
@@ -671,6 +679,7 @@ module.exports = {
return false;
}
if (
+ capIsConstructor &&
isAnonymous &&
parent.left.type === "Identifier" &&
startsWithUpperCase(parent.left.name)
@@ -685,6 +694,7 @@ module.exports = {
*/
case "VariableDeclarator":
return !(
+ capIsConstructor &&
isAnonymous &&
parent.init === currentNode &&
parent.id.type === "Identifier" &&
@@ -1201,6 +1211,23 @@ module.exports = {
},
/**
+ * Gets next location when the result is not out of bound, otherwise returns null.
+ * @param {SourceCode} sourceCode The sourceCode
+ * @param {{line: number, column: number}} location The location
+ * @returns {{line: number, column: number} | null} Next location
+ */
+ getNextLocation(sourceCode, location) {
+ const index = sourceCode.getIndexFromLoc(location);
+
+ // Avoid out of bound location
+ if (index + 1 > sourceCode.text.length) {
+ return null;
+ }
+
+ return sourceCode.getLocFromIndex(index + 1);
+ },
+
+ /**
* Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
* surrounding the node.
* @param {SourceCode} sourceCode The source code object
diff --git a/tools/node_modules/eslint/lib/shared/types.js b/tools/node_modules/eslint/lib/shared/types.js
index 12bd0aed8c..a5bd0200e2 100644
--- a/tools/node_modules/eslint/lib/shared/types.js
+++ b/tools/node_modules/eslint/lib/shared/types.js
@@ -30,6 +30,7 @@ module.exports = {};
* @property {Record<string, boolean>} [env] The environment settings.
* @property {string | string[]} [extends] The path to other config files or the package name of shareable configs.
* @property {Record<string, GlobalConf>} [globals] The global variable settings.
+ * @property {string | string[]} [ignorePatterns] The glob patterns that ignore to lint.
* @property {boolean} [noInlineConfig] The flag that disables directive comments.
* @property {OverrideConfigData[]} [overrides] The override settings per kind of files.
* @property {string} [parser] The path to a parser or the package name of a parser.
@@ -91,6 +92,14 @@ module.exports = {};
* @property {string} message The error message.
* @property {string|null} ruleId The ID of the rule which makes this message.
* @property {0|1|2} severity The severity of this message.
+ * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions.
+ */
+
+/**
+ * @typedef {Object} SuggestionResult
+ * @property {string} desc A short description.
+ * @property {string} [messageId] Id referencing a message for the description.
+ * @property {{ text: string, range: number[] }} fix fix result info
*/
/**
diff --git a/tools/node_modules/eslint/lib/source-code/source-code.js b/tools/node_modules/eslint/lib/source-code/source-code.js
index 86a56803ed..20b442f236 100644
--- a/tools/node_modules/eslint/lib/source-code/source-code.js
+++ b/tools/node_modules/eslint/lib/source-code/source-code.js
@@ -78,6 +78,18 @@ function sortedMerge(tokens, comments) {
return result;
}
+/**
+ * Determines if two nodes or tokens overlap.
+ * @param {ASTNode|Token} first The first node or token to check.
+ * @param {ASTNode|Token} second The second node or token to check.
+ * @returns {boolean} True if the two nodes or tokens overlap.
+ * @private
+ */
+function nodesOrTokensOverlap(first, second) {
+ return (first.range[0] <= second.range[0] && first.range[1] >= second.range[0]) ||
+ (second.range[0] <= first.range[0] && second.range[1] >= first.range[0]);
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@@ -411,19 +423,52 @@ class SourceCode extends TokenStore {
}
/**
- * Determines if two tokens have at least one whitespace character
- * between them. This completely disregards comments in making the
- * determination, so comments count as zero-length substrings.
- * @param {Token} first The token to check after.
- * @param {Token} second The token to check before.
- * @returns {boolean} True if there is only space between tokens, false
- * if there is anything other than whitespace between tokens.
+ * Determines if two nodes or tokens have at least one whitespace character
+ * between them. Order does not matter. Returns false if the given nodes or
+ * tokens overlap.
+ * @param {ASTNode|Token} first The first node or token to check between.
+ * @param {ASTNode|Token} second The second node or token to check between.
+ * @returns {boolean} True if there is a whitespace character between
+ * any of the tokens found between the two given nodes or tokens.
* @public
*/
- isSpaceBetweenTokens(first, second) {
- const text = this.text.slice(first.range[1], second.range[0]);
+ isSpaceBetween(first, second) {
+ if (nodesOrTokensOverlap(first, second)) {
+ return false;
+ }
+
+ const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0]
+ ? [first, second]
+ : [second, first];
+ const firstToken = this.getLastToken(startingNodeOrToken) || startingNodeOrToken;
+ const finalToken = this.getFirstToken(endingNodeOrToken) || endingNodeOrToken;
+ let currentToken = firstToken;
- return /\s/u.test(text.replace(/\/\*.*?\*\//gus, ""));
+ while (currentToken !== finalToken) {
+ const nextToken = this.getTokenAfter(currentToken, { includeComments: true });
+
+ if (currentToken.range[1] !== nextToken.range[0]) {
+ return true;
+ }
+
+ currentToken = nextToken;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if two nodes or tokens have at least one whitespace character
+ * between them. Order does not matter. Returns false if the given nodes or
+ * tokens overlap.
+ * @param {...ASTNode|Token} args The nodes or tokens to check between.
+ * @returns {boolean} True if there is a whitespace character between
+ * any of the tokens found between the two given nodes or tokens.
+ * @deprecated in favor of isSpaceBetween().
+ * @public
+ */
+ isSpaceBetweenTokens(...args) {
+ return this.isSpaceBetween(...args);
}
/**