diff options
author | Rich Trott <rtrott@gmail.com> | 2018-03-21 22:44:47 -0700 |
---|---|---|
committer | Rich Trott <rtrott@gmail.com> | 2018-03-24 04:11:48 -0700 |
commit | 0863a0e528ba9ee01f960dce7ffebe8fff3a774c (patch) | |
tree | 37990f3af668c242ec1c4a9e24afd31b720ad96d /tools/node_modules/eslint/lib | |
parent | 5e00a013eb219716d952cb8eb29a134c00f5db54 (diff) | |
download | android-node-v8-0863a0e528ba9ee01f960dce7ffebe8fff3a774c.tar.gz android-node-v8-0863a0e528ba9ee01f960dce7ffebe8fff3a774c.tar.bz2 android-node-v8-0863a0e528ba9ee01f960dce7ffebe8fff3a774c.zip |
tools: update ESLint to 4.19.1
A few bug fixes result in more stringent linting rules.
PR-URL: https://github.com/nodejs/node/pull/19528
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'tools/node_modules/eslint/lib')
127 files changed, 2064 insertions, 1520 deletions
diff --git a/tools/node_modules/eslint/lib/ast-utils.js b/tools/node_modules/eslint/lib/ast-utils.js index a186bdee54..a188c7fa1c 100644 --- a/tools/node_modules/eslint/lib/ast-utils.js +++ b/tools/node_modules/eslint/lib/ast-utils.js @@ -84,11 +84,10 @@ function isES5Constructor(node) { * @returns {Node|null} A found function node. */ function getUpperFunction(node) { - while (node) { - if (anyFunctionPattern.test(node.type)) { - return node; + for (let currentNode = node; currentNode; currentNode = currentNode.parent) { + if (anyFunctionPattern.test(currentNode.type)) { + return currentNode; } - node = node.parent; } return null; } @@ -132,12 +131,10 @@ function isLoop(node) { * @returns {boolean} `true` if the node is in a loop. */ function isInLoop(node) { - while (node && !isFunction(node)) { - if (isLoop(node)) { + for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) { + if (isLoop(currentNode)) { return true; } - - node = node.parent; } return false; @@ -204,16 +201,14 @@ function isArrayFromMethod(node) { * @returns {boolean} Whether or not the node is a method which has `thisArg`. */ function isMethodWhichHasThisArg(node) { - while (node) { - if (node.type === "Identifier") { - return arrayMethodPattern.test(node.name); - } - if (node.type === "MemberExpression" && !node.computed) { - node = node.property; - continue; + for ( + let currentNode = node; + currentNode.type === "MemberExpression" && !currentNode.computed; + currentNode = currentNode.property + ) { + if (currentNode.property.type === "Identifier") { + return arrayMethodPattern.test(currentNode.property.name); } - - break; } return false; @@ -631,9 +626,10 @@ module.exports = { return false; } const isAnonymous = node.id === null; + let currentNode = node; - while (node) { - const parent = node.parent; + while (currentNode) { + const parent = currentNode.parent; switch (parent.type) { @@ -643,7 +639,7 @@ module.exports = { */ case "LogicalExpression": case "ConditionalExpression": - node = parent; + currentNode = parent; break; /* @@ -663,14 +659,14 @@ module.exports = { if (func === null || !isCallee(func)) { return true; } - node = func.parent; + currentNode = func.parent; break; } case "ArrowFunctionExpression": - if (node !== parent.body || !isCallee(parent)) { + if (currentNode !== parent.body || !isCallee(parent)) { return true; } - node = parent.parent; + currentNode = parent.parent; break; /* @@ -685,7 +681,7 @@ module.exports = { */ case "Property": case "MethodDefinition": - return parent.value !== node; + return parent.value !== currentNode; /* * e.g. @@ -715,7 +711,7 @@ module.exports = { case "VariableDeclarator": return !( isAnonymous && - parent.init === node && + parent.init === currentNode && parent.id.type === "Identifier" && startsWithUpperCase(parent.id.name) ); @@ -728,7 +724,7 @@ module.exports = { */ case "MemberExpression": return ( - parent.object !== node || + parent.object !== currentNode || parent.property.type !== "Identifier" || !bindOrCallOrApplyPattern.test(parent.property.name) || !isCallee(parent) || @@ -746,21 +742,21 @@ module.exports = { if (isReflectApply(parent.callee)) { return ( parent.arguments.length !== 3 || - parent.arguments[0] !== node || + parent.arguments[0] !== currentNode || isNullOrUndefined(parent.arguments[1]) ); } if (isArrayFromMethod(parent.callee)) { return ( parent.arguments.length !== 3 || - parent.arguments[1] !== node || + parent.arguments[1] !== currentNode || isNullOrUndefined(parent.arguments[2]) ); } if (isMethodWhichHasThisArg(parent.callee)) { return ( parent.arguments.length !== 2 || - parent.arguments[0] !== node || + parent.arguments[0] !== currentNode || isNullOrUndefined(parent.arguments[1]) ); } diff --git a/tools/node_modules/eslint/lib/cli-engine.js b/tools/node_modules/eslint/lib/cli-engine.js index 0c1afcbceb..8531d1c1d4 100644 --- a/tools/node_modules/eslint/lib/cli-engine.js +++ b/tools/node_modules/eslint/lib/cli-engine.js @@ -157,8 +157,9 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, repor fileExtension = path.extname(filename); } - filename = filename || "<text>"; - debug(`Linting ${filename}`); + const effectiveFilename = filename || "<text>"; + + debug(`Linting ${effectiveFilename}`); const config = configHelper.getConfig(filePath); if (config.plugins) { @@ -177,18 +178,18 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, repor const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix); const fixedResult = linter.verifyAndFix(text, config, { - filename, + filename: effectiveFilename, allowInlineConfig, reportUnusedDisableDirectives, fix: !!autofixingEnabled && fix, - preprocess: processor && (rawText => processor.preprocess(rawText, filename)), - postprocess: processor && (problemLists => processor.postprocess(problemLists, filename)) + preprocess: processor && (rawText => processor.preprocess(rawText, effectiveFilename)), + postprocess: processor && (problemLists => processor.postprocess(problemLists, effectiveFilename)) }); const stats = calculateStatsPerFile(fixedResult.messages); const result = { - filePath: filename, + filePath: effectiveFilename, messages: fixedResult.messages, errorCount: stats.errorCount, warningCount: stats.warningCount, @@ -302,10 +303,10 @@ function getCacheFile(cacheFile, cwd) { * make sure the path separators are normalized for the environment/os * keeping the trailing path separator if present */ - cacheFile = path.normalize(cacheFile); + const normalizedCacheFile = path.normalize(cacheFile); - const resolvedCacheFile = path.resolve(cwd, cacheFile); - const looksLikeADirectory = cacheFile[cacheFile.length - 1] === path.sep; + const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile); + const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep; /** * return the name for the cache file in case the provided parameter is a directory @@ -368,16 +369,16 @@ class CLIEngine { /** * Creates a new instance of the core CLI engine. - * @param {CLIEngineOptions} options The options for this instance. + * @param {CLIEngineOptions} providedOptions The options for this instance. * @constructor */ - constructor(options) { + constructor(providedOptions) { - options = Object.assign( + const options = Object.assign( Object.create(null), defaultOptions, { cwd: process.cwd() }, - options + providedOptions ); /** @@ -605,20 +606,21 @@ class CLIEngine { ignoredPaths = new IgnoredPaths(options); // resolve filename based on options.cwd (for reporting, ignoredPaths also resolves) - if (filename && !path.isAbsolute(filename)) { - filename = path.resolve(options.cwd, filename); - } - if (filename && ignoredPaths.contains(filename)) { + const resolvedFilename = filename && !path.isAbsolute(filename) + ? path.resolve(options.cwd, filename) + : filename; + + if (resolvedFilename && ignoredPaths.contains(resolvedFilename)) { if (warnIgnored) { - results.push(createIgnoreResult(filename, options.cwd)); + results.push(createIgnoreResult(resolvedFilename, options.cwd)); } } else { results.push( processText( text, configHelper, - filename, + resolvedFilename, options.fix, options.allowInlineConfig, options.reportUnusedDisableDirectives, @@ -672,31 +674,30 @@ class CLIEngine { */ getFormatter(format) { - // default is stylish - format = format || "stylish"; + const resolvedFormatName = format || "stylish"; // only strings are valid formatters - if (typeof format === "string") { + if (typeof resolvedFormatName === "string") { // replace \ with / for Windows compatibility - format = format.replace(/\\/g, "/"); + const normalizedFormatName = resolvedFormatName.replace(/\\/g, "/"); const cwd = this.options ? this.options.cwd : process.cwd(); - const namespace = naming.getNamespaceFromTerm(format); + const namespace = naming.getNamespaceFromTerm(normalizedFormatName); let formatterPath; // if there's a slash, then it's a file - if (!namespace && format.indexOf("/") > -1) { - formatterPath = path.resolve(cwd, format); + if (!namespace && normalizedFormatName.indexOf("/") > -1) { + formatterPath = path.resolve(cwd, normalizedFormatName); } else { try { - const npmFormat = naming.normalizePackageName(format, "eslint-formatter"); + const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); formatterPath = resolver.resolve(npmFormat, `${cwd}/node_modules`); } catch (e) { - formatterPath = `./formatters/${format}`; + formatterPath = `./formatters/${normalizedFormatName}`; } } diff --git a/tools/node_modules/eslint/lib/code-path-analysis/code-path-state.js b/tools/node_modules/eslint/lib/code-path-analysis/code-path-state.js index 0c31e2072b..57da10fa91 100644 --- a/tools/node_modules/eslint/lib/code-path-analysis/code-path-state.js +++ b/tools/node_modules/eslint/lib/code-path-analysis/code-path-state.js @@ -164,13 +164,13 @@ function removeConnection(prevSegments, nextSegments) { * Creates looping path. * * @param {CodePathState} state - The instance. - * @param {CodePathSegment[]} fromSegments - Segments which are source. - * @param {CodePathSegment[]} toSegments - Segments which are destination. + * @param {CodePathSegment[]} unflattenedFromSegments - Segments which are source. + * @param {CodePathSegment[]} unflattenedToSegments - Segments which are destination. * @returns {void} */ -function makeLooped(state, fromSegments, toSegments) { - fromSegments = CodePathSegment.flattenUnusedSegments(fromSegments); - toSegments = CodePathSegment.flattenUnusedSegments(toSegments); +function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) { + const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments); + const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments); const end = Math.min(fromSegments.length, toSegments.length); diff --git a/tools/node_modules/eslint/lib/code-path-analysis/code-path.js b/tools/node_modules/eslint/lib/code-path-analysis/code-path.js index 709a111189..cb26ea18a3 100644 --- a/tools/node_modules/eslint/lib/code-path-analysis/code-path.js +++ b/tools/node_modules/eslint/lib/code-path-analysis/code-path.js @@ -134,14 +134,19 @@ class CodePath { * @returns {void} */ traverseSegments(options, callback) { + let resolvedOptions; + let resolvedCallback; + if (typeof options === "function") { - callback = options; - options = null; + resolvedCallback = options; + resolvedOptions = {}; + } else { + resolvedOptions = options || {}; + resolvedCallback = callback; } - options = options || {}; - const startSegment = options.first || this.internal.initialSegment; - const lastSegment = options.last; + const startSegment = resolvedOptions.first || this.internal.initialSegment; + const lastSegment = resolvedOptions.last; let item = null; let index = 0; @@ -206,7 +211,7 @@ class CodePath { // Call the callback when the first time. if (!skippedSegment) { - callback.call(this, segment, controller); + resolvedCallback.call(this, segment, controller); if (segment === lastSegment) { controller.skip(); } diff --git a/tools/node_modules/eslint/lib/code-path-analysis/fork-context.js b/tools/node_modules/eslint/lib/code-path-analysis/fork-context.js index 4fae6bbb1e..939ed2d0d9 100644 --- a/tools/node_modules/eslint/lib/code-path-analysis/fork-context.js +++ b/tools/node_modules/eslint/lib/code-path-analysis/fork-context.js @@ -46,19 +46,15 @@ function isReachable(segment) { function makeSegments(context, begin, end, create) { const list = context.segmentsList; - if (begin < 0) { - begin = list.length + begin; - } - if (end < 0) { - end = list.length + end; - } + const normalizedBegin = begin >= 0 ? begin : list.length + begin; + const normalizedEnd = end >= 0 ? end : list.length + end; const segments = []; for (let i = 0; i < context.count; ++i) { const allPrevSegments = []; - for (let j = begin; j <= end; ++j) { + for (let j = normalizedBegin; j <= normalizedEnd; ++j) { allPrevSegments.push(list[j][i]); } @@ -79,18 +75,20 @@ function makeSegments(context, begin, end, create) { * @returns {CodePathSegment[]} The merged segments. */ function mergeExtraSegments(context, segments) { - while (segments.length > context.count) { + let currentSegments = segments; + + while (currentSegments.length > context.count) { const merged = []; - for (let i = 0, length = segments.length / 2 | 0; i < length; ++i) { + for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) { merged.push(CodePathSegment.newNext( context.idGenerator.next(), - [segments[i], segments[i + length]] + [currentSegments[i], currentSegments[i + length]] )); } - segments = merged; + currentSegments = merged; } - return segments; + return currentSegments; } //------------------------------------------------------------------------------ diff --git a/tools/node_modules/eslint/lib/config.js b/tools/node_modules/eslint/lib/config.js index b66b9f41e0..7ba5cd6e2d 100644 --- a/tools/node_modules/eslint/lib/config.js +++ b/tools/node_modules/eslint/lib/config.js @@ -51,11 +51,11 @@ function hasRules(options) { class Config { /** - * @param {Object} options Options to be passed in + * @param {Object} providedOptions Options to be passed in * @param {Linter} linterContext Linter instance object */ - constructor(options, linterContext) { - options = options || {}; + constructor(providedOptions, linterContext) { + const options = providedOptions || {}; this.linterContext = linterContext; this.plugins = new Plugins(linterContext.environments, linterContext.rules); @@ -132,11 +132,10 @@ class Config { isResolvable(`eslint-config-${config}`) || config.charAt(0) === "@"; - if (!isNamedConfig) { - config = path.resolve(this.options.cwd, config); - } - - this.specificConfig = ConfigFile.load(config, this); + this.specificConfig = ConfigFile.load( + isNamedConfig ? config : path.resolve(this.options.cwd, config), + this + ); } } diff --git a/tools/node_modules/eslint/lib/config/config-file.js b/tools/node_modules/eslint/lib/config/config-file.js index c5ff073cfc..37ac84831a 100644 --- a/tools/node_modules/eslint/lib/config/config-file.js +++ b/tools/node_modules/eslint/lib/config/config-file.js @@ -400,25 +400,29 @@ function applyExtends(config, configContext, filePath, relativeTo) { } // Make the last element in an array take the highest precedence - config = configExtends.reduceRight((previousValue, parentPath) => { + return configExtends.reduceRight((previousValue, parentPath) => { try { + let extensionPath; + if (parentPath.startsWith("eslint:")) { - parentPath = getEslintCoreConfigPath(parentPath); + extensionPath = getEslintCoreConfigPath(parentPath); } else if (isFilePath(parentPath)) { /* * If the `extends` path is relative, use the directory of the current configuration * file as the reference point. Otherwise, use as-is. */ - parentPath = (path.isAbsolute(parentPath) + extensionPath = (path.isAbsolute(parentPath) ? parentPath : path.join(relativeTo || path.dirname(filePath), parentPath) ); + } else { + extensionPath = parentPath; } - debug(`Loading ${parentPath}`); + debug(`Loading ${extensionPath}`); // eslint-disable-next-line no-use-before-define - return ConfigOps.merge(load(parentPath, configContext, relativeTo), previousValue); + return ConfigOps.merge(load(extensionPath, configContext, relativeTo), previousValue); } catch (e) { /* @@ -432,8 +436,6 @@ function applyExtends(config, configContext, filePath, relativeTo) { } }, config); - - return config; } /** @@ -463,13 +465,20 @@ function resolve(filePath, relativeTo) { normalizedPackageName = naming.normalizePackageName(pluginName, "eslint-plugin"); debug(`Attempting to resolve ${normalizedPackageName}`); - filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); - return { filePath, configName, configFullName }; + + return { + filePath: resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)), + configName, + configFullName + }; } normalizedPackageName = naming.normalizePackageName(filePath, "eslint-config"); debug(`Attempting to resolve ${normalizedPackageName}`); - filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); - return { filePath, configFullName: filePath }; + + return { + filePath: resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)), + configFullName: filePath + }; } diff --git a/tools/node_modules/eslint/lib/config/config-ops.js b/tools/node_modules/eslint/lib/config/config-ops.js index 2ce500a4f4..67c23a8a61 100644 --- a/tools/node_modules/eslint/lib/config/config-ops.js +++ b/tools/node_modules/eslint/lib/config/config-ops.js @@ -136,29 +136,27 @@ module.exports = { const array = Array.isArray(src) || Array.isArray(target); let dst = array && [] || {}; - combine = !!combine; - isRule = !!isRule; if (array) { - target = target || []; + const resolvedTarget = target || []; // src could be a string, so check for array if (isRule && Array.isArray(src) && src.length > 1) { dst = dst.concat(src); } else { - dst = dst.concat(target); + dst = dst.concat(resolvedTarget); } - if (typeof src !== "object" && !Array.isArray(src)) { - src = [src]; - } - Object.keys(src).forEach((e, i) => { - e = src[i]; + const resolvedSrc = typeof src === "object" ? src : [src]; + + Object.keys(resolvedSrc).forEach((_, i) => { + const e = resolvedSrc[i]; + if (typeof dst[i] === "undefined") { dst[i] = e; } else if (typeof e === "object") { if (isRule) { dst[i] = e; } else { - dst[i] = deepmerge(target[i], e, combine, isRule); + dst[i] = deepmerge(resolvedTarget[i], e, combine, isRule); } } else { if (!combine) { diff --git a/tools/node_modules/eslint/lib/config/config-rule.js b/tools/node_modules/eslint/lib/config/config-rule.js index 5fc38ac5d1..27d29446db 100644 --- a/tools/node_modules/eslint/lib/config/config-rule.js +++ b/tools/node_modules/eslint/lib/config/config-rule.js @@ -197,11 +197,10 @@ class RuleConfigSet { * Add a severity level to the front of all configs in the instance. * This should only be called after all configs have been added to the instance. * - * @param {number} [severity=2] The level of severity for the rule (0, 1, 2) * @returns {void} */ - addErrorSeverity(severity) { - severity = severity || 2; + addErrorSeverity() { + const severity = 2; this.ruleConfigs = this.ruleConfigs.map(config => { config.unshift(severity); diff --git a/tools/node_modules/eslint/lib/config/config-validator.js b/tools/node_modules/eslint/lib/config/config-validator.js index 3d98b51045..1a5b3ef13e 100644 --- a/tools/node_modules/eslint/lib/config/config-validator.js +++ b/tools/node_modules/eslint/lib/config/config-validator.js @@ -98,7 +98,8 @@ function validateRuleSchema(rule, localOptions) { * @param {{create: Function}|null} rule The rule that the config is being validated for * @param {string} ruleId The rule's unique name. * @param {array|number} options The given options for the rule. - * @param {string} source The name of the configuration source to report in any errors. + * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined, + * no source is prepended to the message. * @returns {void} */ function validateRuleOptions(rule, ruleId, options, source) { @@ -112,7 +113,13 @@ function validateRuleOptions(rule, ruleId, options, source) { validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []); } } catch (err) { - throw new Error(`${source}:\n\tConfiguration for rule "${ruleId}" is invalid:\n${err.message}`); + const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`; + + if (typeof source === "string") { + throw new Error(`${source}:\n\t${enhancedMessage}`); + } else { + throw new Error(enhancedMessage); + } } } diff --git a/tools/node_modules/eslint/lib/config/plugins.js b/tools/node_modules/eslint/lib/config/plugins.js index c509c48fe2..756f9ff21b 100644 --- a/tools/node_modules/eslint/lib/config/plugins.js +++ b/tools/node_modules/eslint/lib/config/plugins.js @@ -120,6 +120,26 @@ class Plugins { throw pluginLoadErr; } + // This step is costly, so skip if debug is disabled + if (debug.enabled) { + const resolvedPath = require.resolve(longName); + + let version = null; + + try { + version = require(`${longName}/package.json`).version; + } catch (e) { + + // Do nothing + } + + const loadedPluginAndVersion = version + ? `${longName}@${version}` + : `${longName}, version unknown`; + + debug(`Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${resolvedPath})`); + } + this.define(pluginName, plugin); } } diff --git a/tools/node_modules/eslint/lib/file-finder.js b/tools/node_modules/eslint/lib/file-finder.js index 3458bbf52a..11091a99d5 100644 --- a/tools/node_modules/eslint/lib/file-finder.js +++ b/tools/node_modules/eslint/lib/file-finder.js @@ -79,26 +79,25 @@ class FileFinder { * Does not check if a matching directory entry is a file. * Searches for all the file names in this.fileNames. * Is currently used by lib/config.js to find .eslintrc and package.json files. - * @param {string} directory The directory to start the search from. + * @param {string} relativeDirectory The directory to start the search from. * @returns {GeneratorFunction} to iterate the file paths found */ - *findAllInDirectoryAndParents(directory) { + *findAllInDirectoryAndParents(relativeDirectory) { const cache = this.cache; - if (directory) { - directory = path.resolve(this.cwd, directory); - } else { - directory = this.cwd; - } + const initialDirectory = relativeDirectory + ? path.resolve(this.cwd, relativeDirectory) + : this.cwd; - if (cache.hasOwnProperty(directory)) { - yield* cache[directory]; + if (cache.hasOwnProperty(initialDirectory)) { + yield* cache[initialDirectory]; return; // to avoid doing the normal loop afterwards } const dirs = []; const fileNames = this.fileNames; let searched = 0; + let directory = initialDirectory; do { dirs[searched++] = directory; @@ -135,7 +134,7 @@ class FileFinder { // Add what has been cached previously to the cache of each directory searched. for (let i = 0; i < searched; i++) { - dirs.push.apply(cache[dirs[i]], cache[directory]); + [].push.apply(cache[dirs[i]], cache[directory]); } yield* cache[dirs[0]]; diff --git a/tools/node_modules/eslint/lib/ignored-paths.js b/tools/node_modules/eslint/lib/ignored-paths.js index c02e83bc2a..8fff260d02 100644 --- a/tools/node_modules/eslint/lib/ignored-paths.js +++ b/tools/node_modules/eslint/lib/ignored-paths.js @@ -76,7 +76,6 @@ function findPackageJSONFile(cwd) { * @returns {Object} Merged options */ function mergeDefaultOptions(options) { - options = (options || {}); return Object.assign({}, DEFAULT_OPTIONS, options); } @@ -90,10 +89,11 @@ function mergeDefaultOptions(options) { class IgnoredPaths { /** - * @param {Object} options object containing 'ignore', 'ignorePath' and 'patterns' properties + * @param {Object} providedOptions object containing 'ignore', 'ignorePath' and 'patterns' properties */ - constructor(options) { - options = mergeDefaultOptions(options); + constructor(providedOptions) { + const options = mergeDefaultOptions(providedOptions); + this.cache = {}; /** diff --git a/tools/node_modules/eslint/lib/linter.js b/tools/node_modules/eslint/lib/linter.js index 2238c5be0e..8c631522f9 100755..100644 --- a/tools/node_modules/eslint/lib/linter.js +++ b/tools/node_modules/eslint/lib/linter.js @@ -14,7 +14,6 @@ const eslintScope = require("eslint-scope"), levn = require("levn"), lodash = require("lodash"), blankScriptAST = require("../conf/blank-script.json"), - defaultConfig = require("../conf/default-config-options.js"), CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"), ConfigOps = require("./config/config-ops"), validator = require("./config/config-validator"), @@ -33,6 +32,7 @@ const eslintScope = require("eslint-scope"), const debug = require("debug")("eslint:linter"); const MAX_AUTOFIX_PASSES = 10; +const DEFAULT_PARSER_NAME = "espree"; //------------------------------------------------------------------------------ // Typedefs @@ -48,6 +48,14 @@ const MAX_AUTOFIX_PASSES = 10; * @property {Object|null} visitorKeys The visitor keys to traverse this AST. */ +/** + * @typedef {Object} DisableDirective + * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type + * @property {number} line + * @property {number} column + * @property {(string|null)} ruleId + */ + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ @@ -63,25 +71,25 @@ function parseBooleanConfig(string, comment) { const items = {}; // Collapse whitespace around `:` and `,` to make parsing easier - string = string.replace(/\s*([:,])\s*/g, "$1"); + const trimmedString = string.replace(/\s*([:,])\s*/g, "$1"); - string.split(/\s|,+/).forEach(name => { + trimmedString.split(/\s|,+/).forEach(name => { if (!name) { return; } const pos = name.indexOf(":"); - let value; - if (pos !== -1) { - value = name.slice(pos + 1); - name = name.slice(0, pos); + if (pos === -1) { + items[name] = { + value: false, + comment + }; + } else { + items[name.slice(0, pos)] = { + value: name.slice(pos + 1) === "true", + comment + }; } - - items[name] = { - value: (value === "true"), - comment - }; - }); return items; } @@ -119,9 +127,10 @@ function parseJsonConfig(string, location) { * But we are supporting that. So this is a fallback for that. */ items = {}; - string = string.replace(/([a-zA-Z0-9\-/]+):/g, "\"$1\":").replace(/(]|[0-9])\s+(?=")/, "$1,"); + const normalizedString = string.replace(/([a-zA-Z0-9\-/]+):/g, "\"$1\":").replace(/(]|[0-9])\s+(?=")/, "$1,"); + try { - items = JSON.parse(`{${string}}`); + items = JSON.parse(`{${normalizedString}}`); } catch (ex) { return { success: false, @@ -130,7 +139,7 @@ function parseJsonConfig(string, location) { fatal: true, severity: 2, source: null, - message: `Failed to parse JSON from '${string}': ${ex.message}`, + message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`, line: location.start.line, column: location.start.column + 1 } @@ -153,14 +162,12 @@ function parseListConfig(string) { const items = {}; // Collapse whitespace around , - string = string.replace(/\s*,\s*/g, ","); + string.replace(/\s*,\s*/g, ",").split(/,+/).forEach(name => { + const trimmedName = name.trim(); - string.split(/,+/).forEach(name => { - name = name.trim(); - if (!name) { - return; + if (trimmedName) { + items[trimmedName] = true; } - items[name] = true; }); return items; } @@ -170,32 +177,12 @@ function parseListConfig(string) { * and any globals declared by special block comments, are present in the global * scope. * @param {Scope} globalScope The global scope. - * @param {Object} config The existing configuration data. - * @param {Environments} envContext Env context + * @param {Object} configGlobals The globals declared in configuration + * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration * @returns {void} */ -function addDeclaredGlobals(globalScope, config, envContext) { - const declaredGlobals = {}, - exportedGlobals = {}, - explicitGlobals = {}, - builtin = envContext.get("builtin"); - - Object.assign(declaredGlobals, builtin); - - Object.keys(config.env).filter(name => config.env[name]).forEach(name => { - const env = envContext.get(name), - environmentGlobals = env && env.globals; - - if (environmentGlobals) { - Object.assign(declaredGlobals, environmentGlobals); - } - }); - - Object.assign(exportedGlobals, config.exported); - Object.assign(declaredGlobals, config.globals); - Object.assign(explicitGlobals, config.astGlobals); - - Object.keys(declaredGlobals).forEach(name => { +function addDeclaredGlobals(globalScope, configGlobals, commentDirectives) { + Object.keys(configGlobals).forEach(name => { let variable = globalScope.set.get(name); if (!variable) { @@ -204,24 +191,24 @@ function addDeclaredGlobals(globalScope, config, envContext) { globalScope.variables.push(variable); globalScope.set.set(name, variable); } - variable.writeable = declaredGlobals[name]; + variable.writeable = configGlobals[name]; }); - Object.keys(explicitGlobals).forEach(name => { + Object.keys(commentDirectives.enabledGlobals).forEach(name => { let variable = globalScope.set.get(name); if (!variable) { variable = new eslintScope.Variable(name, globalScope); variable.eslintExplicitGlobal = true; - variable.eslintExplicitGlobalComment = explicitGlobals[name].comment; + variable.eslintExplicitGlobalComment = commentDirectives.enabledGlobals[name].comment; globalScope.variables.push(variable); globalScope.set.set(name, variable); } - variable.writeable = explicitGlobals[name].value; + variable.writeable = commentDirectives.enabledGlobals[name].value; }); // mark all exported variables as such - Object.keys(exportedGlobals).forEach(name => { + Object.keys(commentDirectives.exportedVariables).forEach(name => { const variable = globalScope.set.get(name); if (variable) { @@ -260,12 +247,7 @@ function addDeclaredGlobals(globalScope, config, envContext) { * @param {{line: number, column: number}} loc The 0-based location of the comment token * @param {string} value The value after the directive in the comment * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`) - * @returns {{ - * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), - * line: number, - * column: number, - * ruleId: (string|null) - * }[]} Directives from the comment + * @returns {DisableDirective[]} Directives from the comment */ function createDisableDirectives(type, loc, value) { const ruleIds = Object.keys(parseListConfig(value)); @@ -280,92 +262,90 @@ function createDisableDirectives(type, loc, value) { * where reporting is disabled or enabled and merges them with reporting config. * @param {string} filename The file being checked. * @param {ASTNode} ast The top node of the AST. - * @param {Object} config The existing configuration data. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules - * @returns {{ - * config: Object, - * problems: Problem[], - * disableDirectives: { - * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), - * line: number, - * column: number, - * ruleId: (string|null) - * }[] - * }} Modified config object, along with any problems encountered - * while parsing config comments + * @returns {{configuredRules: Object, enabledGlobals: Object, exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}} + * A collection of the directive comments that were found, along with any problems that occurred when parsing */ -function modifyConfigsFromComments(filename, ast, config, ruleMapper) { - - const commentConfig = { - exported: {}, - astGlobals: {}, - rules: {}, - env: {} - }; - const commentRules = {}; +function getDirectiveComments(filename, ast, ruleMapper) { + const configuredRules = {}; + const enabledGlobals = {}; + const exportedVariables = {}; const problems = []; const disableDirectives = []; ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { + const trimmedCommentText = comment.value.trim(); + const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(trimmedCommentText); - let value = comment.value.trim(); - const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value); - - if (match) { - value = value.slice(match.index + match[1].length); - - if (comment.type === "Block") { - switch (match[1]) { - case "exported": - Object.assign(commentConfig.exported, parseBooleanConfig(value, comment)); - break; - - case "globals": - case "global": - Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment)); - break; - - case "eslint-disable": - [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value)); - break; - - case "eslint-enable": - [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value)); - break; - - case "eslint": { - const parseResult = parseJsonConfig(value, comment.loc); - - if (parseResult.success) { - Object.keys(parseResult.config).forEach(name => { - const ruleValue = parseResult.config[name]; - - validator.validateRuleOptions(ruleMapper(name), name, ruleValue, `${filename} line ${comment.loc.start.line}`); - commentRules[name] = ruleValue; - }); - } else { - problems.push(parseResult.error); - } + if (!match) { + return; + } - break; + const directiveValue = trimmedCommentText.slice(match.index + match[1].length); + + if (/^eslint-disable-(next-)?line$/.test(match[1]) && comment.loc.start.line === comment.loc.end.line) { + const directiveType = match[1].slice("eslint-".length); + + [].push.apply(disableDirectives, createDisableDirectives(directiveType, comment.loc.start, directiveValue)); + } else if (comment.type === "Block") { + switch (match[1]) { + case "exported": + Object.assign(exportedVariables, parseBooleanConfig(directiveValue, comment)); + break; + + case "globals": + case "global": + Object.assign(enabledGlobals, parseBooleanConfig(directiveValue, comment)); + break; + + case "eslint-disable": + [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, directiveValue)); + break; + + case "eslint-enable": + [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, directiveValue)); + break; + + case "eslint": { + const parseResult = parseJsonConfig(directiveValue, comment.loc); + + if (parseResult.success) { + Object.keys(parseResult.config).forEach(name => { + const ruleValue = parseResult.config[name]; + + try { + validator.validateRuleOptions(ruleMapper(name), name, ruleValue); + } catch (err) { + problems.push({ + ruleId: name, + severity: 2, + source: null, + message: err.message, + line: comment.loc.start.line, + column: comment.loc.start.column + 1, + endLine: comment.loc.end.line, + endColumn: comment.loc.end.column + 1, + nodeType: null + }); + } + configuredRules[name] = ruleValue; + }); + } else { + problems.push(parseResult.error); } - // no default - } - } else { // comment.type === "Line" - if (match[1] === "eslint-disable-line") { - [].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value)); - } else if (match[1] === "eslint-disable-next-line") { - [].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value)); + break; } + + // no default } } }); - Object.assign(commentConfig.rules, commentRules); - return { - config: ConfigOps.merge(config, commentConfig), + configuredRules, + enabledGlobals, + exportedVariables, problems, disableDirectives }; @@ -381,7 +361,7 @@ function normalizeEcmaVersion(ecmaVersion, isModule) { // Need at least ES6 for modules if (isModule && (!ecmaVersion || ecmaVersion < 6)) { - ecmaVersion = 6; + return 6; } /* @@ -389,87 +369,87 @@ function normalizeEcmaVersion(ecmaVersion, isModule) { * ES2015, which corresponds with ES6 (or a difference of 2009). */ if (ecmaVersion >= 2015) { - ecmaVersion -= 2009; + return ecmaVersion - 2009; } return ecmaVersion; } +const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g; + /** - * Process initial config to make it safe to extend by file comment config - * @param {Object} config Initial config - * @param {Environments} envContext Env context - * @returns {Object} Processed config + * Checks whether or not there is a comment which has "eslint-env *" in a given text. + * @param {string} text - A source code text to check. + * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment. */ -function prepareConfig(config, envContext) { - config.globals = config.globals || {}; - const copiedRules = {}; - let parserOptions = {}; +function findEslintEnv(text) { + let match, retv; - if (typeof config.rules === "object") { - Object.keys(config.rules).forEach(k => { - const rule = config.rules[k]; + eslintEnvPattern.lastIndex = 0; - if (rule === null) { - throw new Error(`Invalid config for rule '${k}'.`); - } - if (Array.isArray(rule)) { - copiedRules[k] = rule.slice(); - } else { - copiedRules[k] = rule; - } - }); + while ((match = eslintEnvPattern.exec(text))) { + retv = Object.assign(retv || {}, parseListConfig(match[1])); } - // merge in environment parserOptions - if (typeof config.env === "object") { - Object.keys(config.env).forEach(envName => { - const env = envContext.get(envName); + return retv; +} - if (config.env[envName] && env && env.parserOptions) { - parserOptions = ConfigOps.merge(parserOptions, env.parserOptions); - } - }); - } +/** + * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a + * consistent shape. + * @param {(string|{reportUnusedDisableDirectives: boolean, filename: string, allowInlineConfig: boolean})} providedOptions Options + * @returns {{reportUnusedDisableDirectives: boolean, filename: string, allowInlineConfig: boolean}} Normalized options + */ +function normalizeVerifyOptions(providedOptions) { + const isObjectOptions = typeof providedOptions === "object"; + const providedFilename = isObjectOptions ? providedOptions.filename : providedOptions; - const preparedConfig = { - rules: copiedRules, - parser: config.parser || defaultConfig.parser, - globals: ConfigOps.merge(defaultConfig.globals, config.globals), - env: ConfigOps.merge(defaultConfig.env, config.env || {}), - settings: ConfigOps.merge(defaultConfig.settings, config.settings || {}), - parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {}) + return { + filename: typeof providedFilename === "string" ? providedFilename : "<input>", + allowInlineConfig: !isObjectOptions || providedOptions.allowInlineConfig !== false, + reportUnusedDisableDirectives: isObjectOptions && !!providedOptions.reportUnusedDisableDirectives }; - const isModule = preparedConfig.parserOptions.sourceType === "module"; +} + +/** + * Combines the provided parserOptions with the options from environments + * @param {Object} providedOptions The provided 'parserOptions' key in a config + * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments + * @returns {Object} Resulting parser options after merge + */ +function resolveParserOptions(providedOptions, enabledEnvironments) { + const parserOptionsFromEnv = enabledEnvironments + .filter(env => env.parserOptions) + .reduce((parserOptions, env) => ConfigOps.merge(parserOptions, env.parserOptions), {}); + + const mergedParserOptions = ConfigOps.merge(parserOptionsFromEnv, providedOptions || {}); + + const isModule = mergedParserOptions.sourceType === "module"; if (isModule) { // can't have global return inside of modules - preparedConfig.parserOptions.ecmaFeatures = Object.assign({}, preparedConfig.parserOptions.ecmaFeatures, { globalReturn: false }); + mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false }); } - preparedConfig.parserOptions.ecmaVersion = normalizeEcmaVersion(preparedConfig.parserOptions.ecmaVersion, isModule); + mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion, isModule); - return preparedConfig; + return mergedParserOptions; } -const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g; - /** - * Checks whether or not there is a comment which has "eslint-env *" in a given text. - * @param {string} text - A source code text to check. - * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment. + * Combines the provided globals object with the globals from environments + * @param {Object} providedGlobals The 'globals' key in a config + * @param {Environments[]} enabledEnvironments The environments enabled in configuration and with inline comments + * @returns {Object} The resolved globals object */ -function findEslintEnv(text) { - let match, retv; - - eslintEnvPattern.lastIndex = 0; - - while ((match = eslintEnvPattern.exec(text))) { - retv = Object.assign(retv || {}, parseListConfig(match[1])); - } - - return retv; +function resolveGlobals(providedGlobals, enabledEnvironments) { + return Object.assign.apply( + null, + [{}] + .concat(enabledEnvironments.filter(env => env.globals).map(env => env.globals)) + .concat(providedGlobals) + ); } /** @@ -532,13 +512,16 @@ function analyzeScope(ast, parserOptions, visitorKeys) { * as possible * @param {string} text The text to parse. * @param {Object} providedParserOptions Options to pass to the parser - * @param {Object} parser The parser module + * @param {string} parserName The name of the parser + * @param {Map<string, Object>} parserMap A map from names to loaded parsers * @param {string} filePath The path to the file being parsed. * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}} * An object containing the AST and parser services if parsing was successful, or the error if parsing failed * @private */ -function parse(text, providedParserOptions, parser, filePath) { +function parse(text, providedParserOptions, parserName, parserMap, filePath) { + + const textToParse = stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`); const parserOptions = Object.assign({}, providedParserOptions, { loc: true, @@ -551,6 +534,25 @@ function parse(text, providedParserOptions, parser, filePath) { filePath }); + let parser; + + try { + parser = parserMap.get(parserName) || require(parserName); + } catch (ex) { + return { + success: false, + error: { + ruleId: null, + fatal: true, + severity: 2, + source: null, + message: ex.message, + line: 0, + column: 0 + } + }; + } + /* * Check for parsing errors first. If there's a parsing error, nothing * else can happen. However, a parsing error does not throw an error @@ -669,6 +671,21 @@ function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) { return false; } +/** + * Runs a rule, and gets its listeners + * @param {Rule} rule A normalized rule with a `create` method + * @param {Context} ruleContext The context that should be passed to the rule + * @returns {Object} A map of selector listeners provided by the rule + */ +function createRuleListeners(rule, ruleContext) { + try { + return rule.create(ruleContext); + } catch (ex) { + ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`; + throw ex; + } +} + // methods that exist on SourceCode object const DEPRECATED_SOURCECODE_PASSTHROUGHS = { getSource: "getText", @@ -707,7 +724,157 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze( ) ); +/** + * Runs the given rules on the given SourceCode object + * @param {SourceCode} sourceCode A SourceCode object for the given text + * @param {Object} configuredRules The rules configuration + * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules + * @param {Object} parserOptions The options that were passed to the parser + * @param {string} parserName The name of the parser in the config + * @param {Object} settings The settings that were enabled in the config + * @param {string} filename The reported filename of the code + * @returns {Problem[]} An array of reported problems + */ +function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename) { + const emitter = createEmitter(); + const traverser = new Traverser(); + + /* + * Create a frozen object with the ruleContext properties and methods that are shared by all rules. + * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the + * properties once for each rule. + */ + const sharedTraversalContext = Object.freeze( + Object.assign( + Object.create(BASE_TRAVERSAL_CONTEXT), + { + getAncestors: () => traverser.parents(), + getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager), + getFilename: () => filename, + getScope: () => getScope(sourceCode.scopeManager, traverser.current(), parserOptions.ecmaVersion), + getSourceCode: () => sourceCode, + markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, traverser.current(), parserOptions, name), + parserOptions, + parserPath: parserName, + parserServices: sourceCode.parserServices, + settings, + + /** + * This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method + * by using the `_linter` property on rule contexts. + * + * This should be removed in a major release after we create a better way to + * lint for unused disable comments. + * https://github.com/eslint/eslint/issues/9193 + */ + _linter: { + report() {}, + on: emitter.on + } + } + ) + ); + + + const lintingProblems = []; + + Object.keys(configuredRules).forEach(ruleId => { + const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]); + + if (severity === 0) { + return; + } + + const rule = ruleMapper(ruleId); + const messageIds = rule.meta && rule.meta.messages; + let reportTranslator = null; + const ruleContext = Object.freeze( + Object.assign( + Object.create(sharedTraversalContext), + { + id: ruleId, + options: getRuleOptions(configuredRules[ruleId]), + report() { + + /* + * Create a report translator lazily. + * In a vast majority of cases, any given rule reports zero errors on a given + * piece of code. Creating a translator lazily avoids the performance cost of + * creating a new translator function for each rule that usually doesn't get + * called. + * + * Using lazy report translators improves end-to-end performance by about 3% + * with Node 8.4.0. + */ + if (reportTranslator === null) { + reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds }); + } + const problem = reportTranslator.apply(null, arguments); + + if (problem.fix && rule.meta && !rule.meta.fixable) { + throw new Error("Fixable rules should export a `meta.fixable` property."); + } + lintingProblems.push(problem); + + /* + * This is used to avoid breaking rules that used monkeypatch Linter, and relied on + * `linter.report` getting called with report info every time a rule reports a problem. + * To continue to support this, make sure that `context._linter.report` is called every + * time a problem is reported by a rule, even though `context._linter` is no longer a + * `Linter` instance. + * + * This should be removed in a major release after we create a better way to + * lint for unused disable comments. + * https://github.com/eslint/eslint/issues/9193 + */ + sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle + problem.ruleId, + problem.severity, + { loc: { start: { line: problem.line, column: problem.column - 1 } } }, + problem.message + ); + } + } + ) + ); + + const ruleListeners = createRuleListeners(rule, ruleContext); + + // add all the selectors from the rule as listeners + Object.keys(ruleListeners).forEach(selector => { + emitter.on( + selector, + timing.enabled + ? timing.time(ruleId, ruleListeners[selector]) + : ruleListeners[selector] + ); + }); + }); + + const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter)); + + /* + * Each node has a type property. Whenever a particular type of + * node is found, an event is fired. This allows any listeners to + * automatically be informed that this type of node has been found + * and react accordingly. + */ + traverser.traverse(sourceCode.ast, { + enter(node, parent) { + node.parent = parent; + eventGenerator.enterNode(node); + }, + leave(node) { + eventGenerator.leaveNode(node); + }, + visitorKeys: sourceCode.visitorKeys + }); + + return lintingProblems; +} + const lastSourceCodes = new WeakMap(); +const loadedParserMaps = new WeakMap(); //------------------------------------------------------------------------------ // Public Interface @@ -721,10 +888,10 @@ module.exports = class Linter { constructor() { lastSourceCodes.set(this, null); + loadedParserMaps.set(this, new Map()); this.version = pkg.version; this.rules = new Rules(); - this._parsers = new Map(); this.environments = new Environments(); } @@ -742,7 +909,7 @@ module.exports = class Linter { /** * Same as linter.verify, except without support for processors. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. - * @param {ESLintConfig} config An ESLintConfig instance to configure everything. + * @param {ESLintConfig} providedConfig An ESLintConfig instance to configure everything. * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked. * If this is not set, the filename will default to '<input>' in the rule context. If * an object, then it has "filename", "saveState", and "allowInlineConfig" properties. @@ -750,23 +917,14 @@ module.exports = class Linter { * Useful if you want to validate JS without comments overriding rules. * @param {boolean} [filenameOrOptions.reportUnusedDisableDirectives=false] Adds reported errors for unused * eslint-disable directives - * @returns {Object[]} The results as an array of messages or null if no messages. + * @returns {Object[]} The results as an array of messages or an empty array if no messages. */ - _verifyWithoutProcessors(textOrSourceCode, config, filenameOrOptions) { - let text, - allowInlineConfig, - providedFilename, - reportUnusedDisableDirectives; + _verifyWithoutProcessors(textOrSourceCode, providedConfig, filenameOrOptions) { + const config = providedConfig || {}; + const options = normalizeVerifyOptions(filenameOrOptions); + let text; // evaluate arguments - if (typeof filenameOrOptions === "object") { - providedFilename = filenameOrOptions.filename; - allowInlineConfig = filenameOrOptions.allowInlineConfig; - reportUnusedDisableDirectives = filenameOrOptions.reportUnusedDisableDirectives; - } else { - providedFilename = filenameOrOptions; - } - if (typeof textOrSourceCode === "string") { lastSourceCodes.set(this, null); text = textOrSourceCode; @@ -775,23 +933,18 @@ module.exports = class Linter { text = textOrSourceCode.text; } - const filename = typeof providedFilename === "string" ? providedFilename : "<input>"; - // search and apply "eslint-env *". const envInFile = findEslintEnv(text); + const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile); + const enabledEnvs = Object.keys(resolvedEnvConfig) + .filter(envName => resolvedEnvConfig[envName]) + .map(envName => this.environments.get(envName)) + .filter(env => env); - config = Object.assign({}, config); - - if (envInFile) { - if (config.env) { - config.env = Object.assign({}, config.env, envInFile); - } else { - config.env = envInFile; - } - } - - // process initial config to make it safe to extend - config = prepareConfig(config, this.environments); + const parserOptions = resolveParserOptions(config.parserOptions || {}, enabledEnvs); + const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs); + const parserName = config.parser || DEFAULT_PARSER_NAME; + const settings = config.settings || {}; if (!lastSourceCodes.get(this)) { @@ -801,26 +954,12 @@ module.exports = class Linter { return []; } - let parser; - - try { - parser = this._parsers.get(config.parser) || require(config.parser); - } catch (ex) { - return [{ - ruleId: null, - fatal: true, - severity: 2, - source: null, - message: ex.message, - line: 0, - column: 0 - }]; - } const parseResult = parse( text, - config.parserOptions, - parser, - filename + parserOptions, + parserName, + loadedParserMaps.get(this), + options.filename ); if (!parseResult.success) { @@ -842,171 +981,41 @@ module.exports = class Linter { ast: lastSourceCode.ast, parserServices: lastSourceCode.parserServices, visitorKeys: lastSourceCode.visitorKeys, - scopeManager: analyzeScope(lastSourceCode.ast, config.parserOptions) + scopeManager: analyzeScope(lastSourceCode.ast, parserOptions) })); } } - const problems = []; const sourceCode = lastSourceCodes.get(this); - let disableDirectives; - - // parse global comments and modify config - if (allowInlineConfig !== false) { - const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, ruleId => this.rules.get(ruleId)); - - config = modifyConfigResult.config; - modifyConfigResult.problems.forEach(problem => problems.push(problem)); - disableDirectives = modifyConfigResult.disableDirectives; - } else { - disableDirectives = []; - } - - const emitter = createEmitter(); - const traverser = new Traverser(); - const scopeManager = sourceCode.scopeManager; - - /* - * Create a frozen object with the ruleContext properties and methods that are shared by all rules. - * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the - * properties once for each rule. - */ - const sharedTraversalContext = Object.freeze( - Object.assign( - Object.create(BASE_TRAVERSAL_CONTEXT), - { - getAncestors: () => traverser.parents(), - getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager), - getFilename: () => filename, - getScope: () => getScope(scopeManager, traverser.current(), config.parserOptions.ecmaVersion), - getSourceCode: () => sourceCode, - markVariableAsUsed: name => markVariableAsUsed(scopeManager, traverser.current(), config.parserOptions, name), - parserOptions: config.parserOptions, - parserPath: config.parser, - parserServices: sourceCode.parserServices, - settings: config.settings, - - /** - * This is used to avoid breaking rules that used to monkeypatch the `Linter#report` method - * by using the `_linter` property on rule contexts. - * - * This should be removed in a major release after we create a better way to - * lint for unused disable comments. - * https://github.com/eslint/eslint/issues/9193 - */ - _linter: { - report() {}, - on: emitter.on - } - } - ) - ); - - // enable appropriate rules - Object.keys(config.rules).forEach(ruleId => { - const severity = ConfigOps.getRuleSeverity(config.rules[ruleId]); - - if (severity === 0) { - return; - } - - const rule = this.rules.get(ruleId); - const messageIds = rule && rule.meta && rule.meta.messages; - let reportTranslator = null; - const ruleContext = Object.freeze( - Object.assign( - Object.create(sharedTraversalContext), - { - id: ruleId, - options: getRuleOptions(config.rules[ruleId]), - report() { - - /* - * Create a report translator lazily. - * In a vast majority of cases, any given rule reports zero errors on a given - * piece of code. Creating a translator lazily avoids the performance cost of - * creating a new translator function for each rule that usually doesn't get - * called. - * - * Using lazy report translators improves end-to-end performance by about 3% - * with Node 8.4.0. - */ - if (reportTranslator === null) { - reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds }); - } - const problem = reportTranslator.apply(null, arguments); - - if (problem.fix && rule.meta && !rule.meta.fixable) { - throw new Error("Fixable rules should export a `meta.fixable` property."); - } - problems.push(problem); - - /* - * This is used to avoid breaking rules that used monkeypatch Linter, and relied on - * `linter.report` getting called with report info every time a rule reports a problem. - * To continue to support this, make sure that `context._linter.report` is called every - * time a problem is reported by a rule, even though `context._linter` is no longer a - * `Linter` instance. - * - * This should be removed in a major release after we create a better way to - * lint for unused disable comments. - * https://github.com/eslint/eslint/issues/9193 - */ - sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle - problem.ruleId, - problem.severity, - { loc: { start: { line: problem.line, column: problem.column - 1 } } }, - problem.message - ); - } - } - ) - ); - - try { - const ruleListeners = rule.create(ruleContext); - - // add all the selectors from the rule as listeners - Object.keys(ruleListeners).forEach(selector => { - emitter.on( - selector, - timing.enabled - ? timing.time(ruleId, ruleListeners[selector]) - : ruleListeners[selector] - ); - }); - } catch (ex) { - ex.message = `Error while loading rule '${ruleId}': ${ex.message}`; - throw ex; - } - }); + const commentDirectives = options.allowInlineConfig + ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => this.rules.get(ruleId)) + : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] }; // augment global scope with declared global variables - addDeclaredGlobals(scopeManager.scopes[0], config, this.environments); + addDeclaredGlobals( + sourceCode.scopeManager.scopes[0], + configuredGlobals, + { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals } + ); - const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter)); + const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules); - /* - * Each node has a type property. Whenever a particular type of - * node is found, an event is fired. This allows any listeners to - * automatically be informed that this type of node has been found - * and react accordingly. - */ - traverser.traverse(sourceCode.ast, { - enter(node, parent) { - node.parent = parent; - eventGenerator.enterNode(node); - }, - leave(node) { - eventGenerator.leaveNode(node); - }, - visitorKeys: sourceCode.visitorKeys - }); + const lintingProblems = runRules( + sourceCode, + configuredRules, + ruleId => this.rules.get(ruleId), + parserOptions, + parserName, + settings, + options.filename + ); return applyDisableDirectives({ - directives: disableDirectives, - problems: problems.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), - reportUnusedDisableDirectives + directives: commentDirectives.disableDirectives, + problems: lintingProblems + .concat(commentDirectives.problems) + .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), + reportUnusedDisableDirectives: options.reportUnusedDisableDirectives }); } @@ -1026,7 +1035,7 @@ module.exports = class Linter { * @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided, * this should accept an array of the message lists for each code block returned from the preprocessor, * apply a mapping to the messages as appropriate, and return a one-dimensional array of messages - * @returns {Object[]} The results as an array of messages or null if no messages. + * @returns {Object[]} The results as an array of messages or an empty array if no messages. */ verify(textOrSourceCode, config, filenameOrOptions) { const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]); @@ -1083,7 +1092,7 @@ module.exports = class Linter { * @returns {void} */ defineParser(parserId, parserModule) { - this._parsers.set(parserId, parserModule); + loadedParserMaps.get(this).set(parserId, parserModule); } /** @@ -1108,7 +1117,8 @@ module.exports = class Linter { let messages = [], fixedResult, fixed = false, - passNumber = 0; + passNumber = 0, + currentText = text; const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`; const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true; @@ -1125,10 +1135,10 @@ module.exports = class Linter { passNumber++; debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`); - messages = this.verify(text, config, options); + messages = this.verify(currentText, config, options); debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`); - fixedResult = SourceCodeFixer.applyFixes(text, messages, shouldFix); + fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix); /* * stop if there are any syntax errors. @@ -1142,7 +1152,7 @@ module.exports = class Linter { fixed = fixed || fixedResult.fixed; // update to use the fixed output instead of the original text - text = fixedResult.output; + currentText = fixedResult.output; } while ( fixedResult.fixed && @@ -1154,12 +1164,12 @@ module.exports = class Linter { * the most up-to-date information. */ if (fixedResult.fixed) { - fixedResult.messages = this.verify(text, config, options); + fixedResult.messages = this.verify(currentText, config, options); } // ensure the last result properly reflects if fixes were done fixedResult.fixed = fixed; - fixedResult.output = text; + fixedResult.output = currentText; return fixedResult; } diff --git a/tools/node_modules/eslint/lib/load-rules.js b/tools/node_modules/eslint/lib/load-rules.js index b74905d65a..a9da956bdd 100644 --- a/tools/node_modules/eslint/lib/load-rules.js +++ b/tools/node_modules/eslint/lib/load-rules.js @@ -20,16 +20,15 @@ const rulesDirCache = {}; /** * Load all rule modules from specified directory. - * @param {string} [rulesDir] Path to rules directory, may be relative. Defaults to `lib/rules`. + * @param {string} [relativeRulesDir] Path to rules directory, may be relative. Defaults to `lib/rules`. * @param {string} cwd Current working directory * @returns {Object} Loaded rule modules by rule ids (file names). */ -module.exports = function(rulesDir, cwd) { - if (!rulesDir) { - rulesDir = path.join(__dirname, "rules"); - } else { - rulesDir = path.resolve(cwd, rulesDir); - } +module.exports = function(relativeRulesDir, cwd) { + + const rulesDir = relativeRulesDir + ? path.resolve(cwd, relativeRulesDir) + : path.join(__dirname, "rules"); // cache will help performance as IO operation are expensive if (rulesDirCache[rulesDir]) { diff --git a/tools/node_modules/eslint/lib/options.js b/tools/node_modules/eslint/lib/options.js index c8005d4445..9265d151d5 100644 --- a/tools/node_modules/eslint/lib/options.js +++ b/tools/node_modules/eslint/lib/options.js @@ -27,16 +27,16 @@ module.exports = optionator({ heading: "Basic configuration" }, { - option: "config", - alias: "c", - type: "path::String", - description: "Use configuration from this file or shareable config" - }, - { option: "eslintrc", type: "Boolean", default: "true", - description: "Disable use of configuration from .eslintrc" + description: "Disable use of configuration from .eslintrc.*" + }, + { + option: "config", + alias: "c", + type: "path::String", + description: "Use this configuration, overriding .eslintrc.* config options if present" }, { option: "env", diff --git a/tools/node_modules/eslint/lib/report-translator.js b/tools/node_modules/eslint/lib/report-translator.js index 8e5b587f96..7893a1f7ad 100644 --- a/tools/node_modules/eslint/lib/report-translator.js +++ b/tools/node_modules/eslint/lib/report-translator.js @@ -28,6 +28,22 @@ const interpolate = require("./util/interpolate"); * @property {Function} [fix] The function to call that creates a fix command. */ +/** + * Information about the report + * @typedef {Object} ReportInfo + * @property {string} ruleId + * @property {(0|1|2)} severity + * @property {(string|undefined)} message + * @property {(string|undefined)} messageId + * @property {number} line + * @property {number} column + * @property {(number|undefined)} endLine + * @property {(number|undefined)} endColumn + * @property {(string|null)} nodeType + * @property {string} source + * @property {({text: string, range: (number[]|null)}|null)} fix + */ + //------------------------------------------------------------------------------ // Module Definition //------------------------------------------------------------------------------ @@ -121,7 +137,7 @@ function compareFixesByRange(a, b) { * Merges the given fixes array into one. * @param {Fix[]} fixes The fixes to merge. * @param {SourceCode} sourceCode The source code object to get the text between fixes. - * @returns {{text: string, range: [number, number]}} The merged fixes + * @returns {{text: string, range: number[]}} The merged fixes */ function mergeFixes(fixes, sourceCode) { if (fixes.length === 0) { @@ -158,7 +174,7 @@ function mergeFixes(fixes, sourceCode) { * If the descriptor retrieves multiple fixes, this merges those to one. * @param {MessageDescriptor} descriptor The report descriptor. * @param {SourceCode} sourceCode The source code object to get text between fixes. - * @returns {({text: string, range: [number, number]}|null)} The fix for the descriptor + * @returns {({text: string, range: number[]}|null)} The fix for the descriptor */ function normalizeFixes(descriptor, sourceCode) { if (typeof descriptor.fix !== "function") { @@ -177,27 +193,15 @@ function normalizeFixes(descriptor, sourceCode) { /** * Creates information about the report from a descriptor - * @param {{ - * ruleId: string, - * severity: (0|1|2), - * node: (ASTNode|null), - * message: string, - * loc: {start: SourceLocation, end: (SourceLocation|null)}, - * fix: ({text: string, range: [number, number]}|null), - * sourceLines: string[] - * }} options Information about the problem - * @returns {function(...args): { - * ruleId: string, - * severity: (0|1|2), - * message: string, - * line: number, - * column: number, - * endLine: (number|undefined), - * endColumn: (number|undefined), - * nodeType: (string|null), - * source: string, - * fix: ({text: string, range: [number, number]}|null) - * }} Information about the report + * @param {Object} options Information about the problem + * @param {string} options.ruleId Rule ID + * @param {(0|1|2)} options.severity Rule severity + * @param {(ASTNode|null)} options.node Node + * @param {string} options.message Error message + * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location + * @param {{text: string, range: (number[]|null)}} options.fix The fix object + * @param {string[]} options.sourceLines Source lines + * @returns {function(...args): ReportInfo} Function that returns information about the report */ function createProblem(options) { const problem = { @@ -235,20 +239,7 @@ function createProblem(options) { * problem for the Node.js API. * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object}} metadata Metadata for the reported problem * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted - * @returns {function(...args): { - * ruleId: string, - * severity: (0|1|2), - * message: (string|undefined), - * messageId: (string|undefined), - * line: number, - * column: number, - * endLine: (number|undefined), - * endColumn: (number|undefined), - * nodeType: (string|null), - * source: string, - * fix: ({text: string, range: [number, number]}|null) - * }} - * Information about the report + * @returns {function(...args): ReportInfo} Function that returns information about the report */ module.exports = function createReportTranslator(metadata) { diff --git a/tools/node_modules/eslint/lib/rules/accessor-pairs.js b/tools/node_modules/eslint/lib/rules/accessor-pairs.js index b01b29b176..6860729543 100644 --- a/tools/node_modules/eslint/lib/rules/accessor-pairs.js +++ b/tools/node_modules/eslint/lib/rules/accessor-pairs.js @@ -58,11 +58,11 @@ function isPropertyDescriptor(node) { * Object.defineProperties(obj, {foo: {set: ...}}) * Object.create(proto, {foo: {set: ...}}) */ - node = node.parent.parent; + const grandparent = node.parent.parent; - return node.type === "ObjectExpression" && ( - isArgumentOfMethodCall(node, 1, "Object", "create") || - isArgumentOfMethodCall(node, 1, "Object", "defineProperties") + return grandparent.type === "ObjectExpression" && ( + isArgumentOfMethodCall(grandparent, 1, "Object", "create") || + isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties") ); } @@ -89,7 +89,11 @@ module.exports = { } }, additionalProperties: false - }] + }], + messages: { + getter: "Getter is not present.", + setter: "Setter is not present." + } }, create(context) { const config = context.options[0] || {}; @@ -140,9 +144,9 @@ module.exports = { } if (checkSetWithoutGet && isSetPresent && !isGetPresent) { - context.report({ node, message: "Getter is not present." }); + context.report({ node, messageId: "getter" }); } else if (checkGetWithoutSet && isGetPresent && !isSetPresent) { - context.report({ node, message: "Setter is not present." }); + context.report({ node, messageId: "setter" }); } } diff --git a/tools/node_modules/eslint/lib/rules/array-bracket-newline.js b/tools/node_modules/eslint/lib/rules/array-bracket-newline.js index b939d65b3f..e190405726 100644 --- a/tools/node_modules/eslint/lib/rules/array-bracket-newline.js +++ b/tools/node_modules/eslint/lib/rules/array-bracket-newline.js @@ -41,7 +41,13 @@ module.exports = { } ] } - ] + ], + messages: { + unexpectedOpeningLinebreak: "There should be no linebreak after '['.", + unexpectedClosingLinebreak: "There should be no linebreak before ']'.", + missingOpeningLinebreak: "A linebreak is required after '['.", + missingClosingLinebreak: "A linebreak is required before ']'." + } }, create(context) { @@ -106,7 +112,7 @@ module.exports = { context.report({ node, loc: token.loc, - message: "There should be no linebreak after '['.", + messageId: "unexpectedOpeningLinebreak", fix(fixer) { const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }); @@ -129,7 +135,7 @@ module.exports = { context.report({ node, loc: token.loc, - message: "There should be no linebreak before ']'.", + messageId: "unexpectedClosingLinebreak", fix(fixer) { const previousToken = sourceCode.getTokenBefore(token, { includeComments: true }); @@ -152,7 +158,7 @@ module.exports = { context.report({ node, loc: token.loc, - message: "A linebreak is required after '['.", + messageId: "missingOpeningLinebreak", fix(fixer) { return fixer.insertTextAfter(token, "\n"); } @@ -169,7 +175,7 @@ module.exports = { context.report({ node, loc: token.loc, - message: "A linebreak is required before ']'.", + messageId: "missingClosingLinebreak", fix(fixer) { return fixer.insertTextBefore(token, "\n"); } diff --git a/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js b/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js index 933458a42a..c64ff3dc27 100644 --- a/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js +++ b/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js @@ -38,7 +38,13 @@ module.exports = { }, additionalProperties: false } - ] + ], + messages: { + unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", + unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", + missingSpaceAfter: "A space is required after '{{tokenValue}}'.", + missingSpaceBefore: "A space is required before '{{tokenValue}}'." + } }, create(context) { const spaced = context.options[0] === "always", @@ -76,7 +82,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "There should be no space after '{{tokenValue}}'.", + messageId: "unexpectedSpaceAfter", data: { tokenValue: token.value }, @@ -98,7 +104,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "There should be no space before '{{tokenValue}}'.", + messageId: "unexpectedSpaceBefore", data: { tokenValue: token.value }, @@ -120,7 +126,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "A space is required after '{{tokenValue}}'.", + messageId: "missingSpaceAfter", data: { tokenValue: token.value }, @@ -140,7 +146,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "A space is required before '{{tokenValue}}'.", + messageId: "missingSpaceBefore", data: { tokenValue: token.value }, diff --git a/tools/node_modules/eslint/lib/rules/array-callback-return.js b/tools/node_modules/eslint/lib/rules/array-callback-return.js index 69655481d8..2375dcba9b 100644 --- a/tools/node_modules/eslint/lib/rules/array-callback-return.js +++ b/tools/node_modules/eslint/lib/rules/array-callback-return.js @@ -71,8 +71,10 @@ function isTargetMethod(node) { * @returns {boolean} `true` if the node is the callback of an array method. */ function isCallbackOfArrayMethod(node) { - while (node) { - const parent = node.parent; + let currentNode = node; + + while (currentNode) { + const parent = currentNode.parent; switch (parent.type) { @@ -82,7 +84,7 @@ function isCallbackOfArrayMethod(node) { */ case "LogicalExpression": case "ConditionalExpression": - node = parent; + currentNode = parent; break; /* @@ -99,7 +101,7 @@ function isCallbackOfArrayMethod(node) { if (func === null || !astUtils.isCallee(func)) { return false; } - node = func.parent; + currentNode = func.parent; break; } @@ -112,13 +114,13 @@ function isCallbackOfArrayMethod(node) { if (astUtils.isArrayFromMethod(parent.callee)) { return ( parent.arguments.length >= 2 && - parent.arguments[1] === node + parent.arguments[1] === currentNode ); } if (isTargetMethod(parent.callee)) { return ( parent.arguments.length >= 1 && - parent.arguments[0] === node + parent.arguments[0] === currentNode ); } return false; @@ -156,7 +158,13 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + expectedAtEnd: "Expected to return a value at the end of {{name}}.", + expectedInside: "Expected to return a value in {{name}}.", + expectedReturnValue: "{{name}} expected a return value." + } }, create(context) { @@ -188,9 +196,9 @@ module.exports = { context.report({ node, loc: getLocation(node, context.getSourceCode()).loc.start, - message: funcInfo.hasReturn - ? "Expected to return a value at the end of {{name}}." - : "Expected to return a value in {{name}}.", + messageId: funcInfo.hasReturn + ? "expectedAtEnd" + : "expectedInside", data: { name: astUtils.getFunctionNameWithKind(funcInfo.node) } @@ -230,7 +238,7 @@ module.exports = { if (!options.allowImplicit && !node.argument) { context.report({ node, - message: "{{name}} expected a return value.", + messageId: "expectedReturnValue", data: { name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)) } diff --git a/tools/node_modules/eslint/lib/rules/array-element-newline.js b/tools/node_modules/eslint/lib/rules/array-element-newline.js index 22352c7e33..359b8d436e 100644 --- a/tools/node_modules/eslint/lib/rules/array-element-newline.js +++ b/tools/node_modules/eslint/lib/rules/array-element-newline.js @@ -41,7 +41,12 @@ module.exports = { } ] } - ] + ], + + messages: { + unexpectedLineBreak: "There should be no linebreak here.", + missingLineBreak: "There should be a linebreak after this element." + } }, create(context) { @@ -54,16 +59,16 @@ module.exports = { /** * Normalizes a given option value. * - * @param {string|Object|undefined} option - An option value to parse. + * @param {string|Object|undefined} providedOption - An option value to parse. * @returns {{multiline: boolean, minItems: number}} Normalized option object. */ - function normalizeOptionValue(option) { + function normalizeOptionValue(providedOption) { let multiline = false; let minItems; - option = option || "always"; + const option = providedOption || "always"; - if (option === "always" || option.minItems === 0) { + if (!option || option === "always" || option.minItems === 0) { minItems = 0; } else if (option === "never") { minItems = Number.POSITIVE_INFINITY; @@ -100,7 +105,7 @@ module.exports = { start: tokenBefore.loc.end, end: token.loc.start }, - message: "There should be no linebreak here.", + messageId: "unexpectedLineBreak", fix(fixer) { if (astUtils.isCommentToken(tokenBefore)) { return null; @@ -149,7 +154,7 @@ module.exports = { start: tokenBefore.loc.end, end: token.loc.start }, - message: "There should be a linebreak after this element.", + messageId: "missingLineBreak", fix(fixer) { return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n"); } diff --git a/tools/node_modules/eslint/lib/rules/arrow-body-style.js b/tools/node_modules/eslint/lib/rules/arrow-body-style.js index afbf46fd54..1f0d1377b5 100644 --- a/tools/node_modules/eslint/lib/rules/arrow-body-style.js +++ b/tools/node_modules/eslint/lib/rules/arrow-body-style.js @@ -55,7 +55,15 @@ module.exports = { ] }, - fixable: "code" + fixable: "code", + + messages: { + unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.", + unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.", + unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.", + unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.", + expectedBlock: "Expected block statement surrounding arrow body." + } }, create(context) { @@ -110,22 +118,22 @@ module.exports = { } if (never || asNeeded && blockBody[0].type === "ReturnStatement") { - let message; + let messageId; if (blockBody.length === 0) { - message = "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`."; + messageId = "unexpectedEmptyBlock"; } else if (blockBody.length > 1) { - message = "Unexpected block statement surrounding arrow body."; + messageId = "unexpectedOtherBlock"; } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) { - message = "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`."; + messageId = "unexpectedObjectBlock"; } else { - message = "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`."; + messageId = "unexpectedSingleBlock"; } context.report({ node, loc: arrowBody.loc.start, - message, + messageId, fix(fixer) { const fixes = []; @@ -190,7 +198,7 @@ module.exports = { context.report({ node, loc: arrowBody.loc.start, - message: "Expected block statement surrounding arrow body.", + messageId: "expectedBlock", fix(fixer) { const fixes = []; const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken); diff --git a/tools/node_modules/eslint/lib/rules/arrow-parens.js b/tools/node_modules/eslint/lib/rules/arrow-parens.js index 5a0fd61b34..8cabb7f90f 100644 --- a/tools/node_modules/eslint/lib/rules/arrow-parens.js +++ b/tools/node_modules/eslint/lib/rules/arrow-parens.js @@ -38,15 +38,19 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpectedParens: "Unexpected parentheses around single function argument.", + expectedParens: "Expected parentheses around arrow function argument.", + + unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.", + expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces." + } }, create(context) { - const message = "Expected parentheses around arrow function argument."; - const asNeededMessage = "Unexpected parentheses around single function argument."; const asNeeded = context.options[0] === "as-needed"; - const requireForBlockBodyMessage = "Unexpected parentheses around single function argument having a body with no curly braces"; - const requireForBlockBodyNoParensMessage = "Expected parentheses around arrow function argument having a body with curly braces."; const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true; const sourceCode = context.getSourceCode(); @@ -94,7 +98,7 @@ module.exports = { if (astUtils.isOpeningParenToken(firstTokenOfParam)) { context.report({ node, - message: requireForBlockBodyMessage, + messageId: "unexpectedParensInline", fix: fixParamsWithParenthesis }); } @@ -108,7 +112,7 @@ module.exports = { if (!astUtils.isOpeningParenToken(firstTokenOfParam)) { context.report({ node, - message: requireForBlockBodyNoParensMessage, + messageId: "expectedParensBlock", fix(fixer) { return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); } @@ -127,7 +131,7 @@ module.exports = { if (astUtils.isOpeningParenToken(firstTokenOfParam)) { context.report({ node, - message: asNeededMessage, + messageId: "unexpectedParens", fix: fixParamsWithParenthesis }); } @@ -141,7 +145,7 @@ module.exports = { if (after.value !== ")") { context.report({ node, - message, + messageId: "expectedParens", fix(fixer) { return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); } diff --git a/tools/node_modules/eslint/lib/rules/arrow-spacing.js b/tools/node_modules/eslint/lib/rules/arrow-spacing.js index d2c5b9b77b..52f8fcab97 100644 --- a/tools/node_modules/eslint/lib/rules/arrow-spacing.js +++ b/tools/node_modules/eslint/lib/rules/arrow-spacing.js @@ -38,7 +38,15 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + expectedBefore: "Missing space before =>.", + unexpectedBefore: "Unexpected space before =>.", + + expectedAfter: "Missing space after =>.", + unexpectedAfter: "Unexpected space after =>." + } }, create(context) { @@ -96,7 +104,7 @@ module.exports = { if (countSpace.before === 0) { context.report({ node: tokens.before, - message: "Missing space before =>.", + messageId: "expectedBefore", fix(fixer) { return fixer.insertTextBefore(tokens.arrow, " "); } @@ -108,7 +116,7 @@ module.exports = { if (countSpace.before > 0) { context.report({ node: tokens.before, - message: "Unexpected space before =>.", + messageId: "unexpectedBefore", fix(fixer) { return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]); } @@ -122,7 +130,7 @@ module.exports = { if (countSpace.after === 0) { context.report({ node: tokens.after, - message: "Missing space after =>.", + messageId: "expectedAfter", fix(fixer) { return fixer.insertTextAfter(tokens.arrow, " "); } @@ -134,7 +142,7 @@ module.exports = { if (countSpace.after > 0) { context.report({ node: tokens.after, - message: "Unexpected space after =>.", + messageId: "unexpectedAfter", fix(fixer) { return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]); } diff --git a/tools/node_modules/eslint/lib/rules/block-scoped-var.js b/tools/node_modules/eslint/lib/rules/block-scoped-var.js index 58cef1741a..1000fbc83c 100644 --- a/tools/node_modules/eslint/lib/rules/block-scoped-var.js +++ b/tools/node_modules/eslint/lib/rules/block-scoped-var.js @@ -17,7 +17,11 @@ module.exports = { url: "https://eslint.org/docs/rules/block-scoped-var" }, - schema: [] + schema: [], + + messages: { + outOfScope: "'{{name}}' used outside of binding context." + } }, create(context) { @@ -48,7 +52,7 @@ module.exports = { function report(reference) { const identifier = reference.identifier; - context.report({ node: identifier, message: "'{{name}}' used outside of binding context.", data: { name: identifier.name } }); + context.report({ node: identifier, messageId: "outOfScope", data: { name: identifier.name } }); } /** diff --git a/tools/node_modules/eslint/lib/rules/block-spacing.js b/tools/node_modules/eslint/lib/rules/block-spacing.js index 4fbf6d4c0b..536381362a 100644 --- a/tools/node_modules/eslint/lib/rules/block-spacing.js +++ b/tools/node_modules/eslint/lib/rules/block-spacing.js @@ -24,12 +24,17 @@ module.exports = { schema: [ { enum: ["always", "never"] } - ] + ], + + messages: { + missing: "Requires a space {{location}} '{{token}}'", + extra: "Unexpected space(s) {{location}} '{{token}}'" + } }, create(context) { const always = (context.options[0] !== "never"), - message = always ? "Requires a space" : "Unexpected space(s)", + messageId = always ? "missing" : "extra", sourceCode = context.getSourceCode(); /** @@ -98,9 +103,10 @@ module.exports = { context.report({ node, loc: openBrace.loc.start, - message: "{{message}} after '{'.", + messageId, data: { - message + location: "after", + token: openBrace.value }, fix(fixer) { if (always) { @@ -115,9 +121,10 @@ module.exports = { context.report({ node, loc: closeBrace.loc.start, - message: "{{message}} before '}'.", + messageId, data: { - message + location: "before", + token: closeBrace.value }, fix(fixer) { if (always) { diff --git a/tools/node_modules/eslint/lib/rules/brace-style.js b/tools/node_modules/eslint/lib/rules/brace-style.js index bc0ddbd78f..e2cbafe219 100644 --- a/tools/node_modules/eslint/lib/rules/brace-style.js +++ b/tools/node_modules/eslint/lib/rules/brace-style.js @@ -35,7 +35,16 @@ module.exports = { } ], - fixable: "whitespace" + fixable: "whitespace", + + messages: { + nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.", + sameLineOpen: "Opening curly brace appears on the same line as controlling statement.", + blockSameLine: "Statement inside of curly braces should be on next line.", + nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.", + singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", + sameLineClose: "Closing curly brace appears on the same line as the subsequent block." + } }, create(context) { @@ -43,13 +52,6 @@ module.exports = { params = context.options[1] || {}, sourceCode = context.getSourceCode(); - const OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.", - OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.", - BODY_MESSAGE = "Statement inside of curly braces should be on next line.", - CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.", - CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", - CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block."; - //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -86,7 +88,7 @@ module.exports = { if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) { context.report({ node: openingCurly, - message: OPEN_MESSAGE, + messageId: "nextLineOpen", fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly) }); } @@ -94,7 +96,7 @@ module.exports = { if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) { context.report({ node: openingCurly, - message: OPEN_MESSAGE_ALLMAN, + messageId: "sameLineOpen", fix: fixer => fixer.insertTextBefore(openingCurly, "\n") }); } @@ -102,7 +104,7 @@ module.exports = { if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) { context.report({ node: openingCurly, - message: BODY_MESSAGE, + messageId: "blockSameLine", fix: fixer => fixer.insertTextAfter(openingCurly, "\n") }); } @@ -110,7 +112,7 @@ module.exports = { if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) { context.report({ node: closingCurly, - message: CLOSE_MESSAGE_SINGLE, + messageId: "singleLineClose", fix: fixer => fixer.insertTextBefore(closingCurly, "\n") }); } @@ -127,7 +129,7 @@ module.exports = { if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { context.report({ node: curlyToken, - message: CLOSE_MESSAGE, + messageId: "nextLineClose", fix: removeNewlineBetween(curlyToken, keywordToken) }); } @@ -135,7 +137,7 @@ module.exports = { if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { context.report({ node: curlyToken, - message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN, + messageId: "sameLineClose", fix: fixer => fixer.insertTextAfter(curlyToken, "\n") }); } diff --git a/tools/node_modules/eslint/lib/rules/callback-return.js b/tools/node_modules/eslint/lib/rules/callback-return.js index ed85c7181a..f55fed87db 100644 --- a/tools/node_modules/eslint/lib/rules/callback-return.js +++ b/tools/node_modules/eslint/lib/rules/callback-return.js @@ -20,7 +20,11 @@ module.exports = { schema: [{ type: "array", items: { type: "string" } - }] + }], + + messages: { + missingReturn: "Expected return with your callback function." + } }, create(context) { @@ -166,7 +170,7 @@ module.exports = { // as long as you're the child of a function at this point you should be asked to return if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { - context.report({ node, message: "Expected return with your callback function." }); + context.report({ node, messageId: "missingReturn" }); } } diff --git a/tools/node_modules/eslint/lib/rules/camelcase.js b/tools/node_modules/eslint/lib/rules/camelcase.js index 86822f9752..84db54ff28 100644 --- a/tools/node_modules/eslint/lib/rules/camelcase.js +++ b/tools/node_modules/eslint/lib/rules/camelcase.js @@ -28,7 +28,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + notCamelCase: "Identifier '{{name}}' is not in camel case." + } }, create(context) { @@ -62,7 +66,7 @@ module.exports = { function report(node) { if (reported.indexOf(node) < 0) { reported.push(node); - context.report({ node, message: "Identifier '{{name}}' is not in camel case.", data: { name: node.name } }); + context.report({ node, messageId: "notCamelCase", data: { name: node.name } }); } } diff --git a/tools/node_modules/eslint/lib/rules/capitalized-comments.js b/tools/node_modules/eslint/lib/rules/capitalized-comments.js index 19b5f6a717..c98a4464ac 100644 --- a/tools/node_modules/eslint/lib/rules/capitalized-comments.js +++ b/tools/node_modules/eslint/lib/rules/capitalized-comments.js @@ -15,9 +15,7 @@ const astUtils = require("../ast-utils"); // Helpers //------------------------------------------------------------------------------ -const ALWAYS_MESSAGE = "Comments should not begin with a lowercase character", - NEVER_MESSAGE = "Comments should not begin with an uppercase character", - DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, +const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, WHITESPACE = /\s/g, MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/, // TODO: Combine w/ max-len pattern? DEFAULTS = { @@ -132,7 +130,12 @@ module.exports = { } ] } - ] + ], + + messages: { + unexpectedLowercaseComment: "Comments should not begin with a lowercase character", + unexpectedUppercaseComment: "Comments should not begin with an uppercase character" + } }, create(context) { @@ -267,14 +270,14 @@ module.exports = { commentValid = isCommentValid(comment, options); if (!commentValid) { - const message = capitalize === "always" - ? ALWAYS_MESSAGE - : NEVER_MESSAGE; + const messageId = capitalize === "always" + ? "unexpectedLowercaseComment" + : "unexpectedUppercaseComment"; context.report({ node: null, // Intentionally using loc instead loc: comment.loc, - message, + messageId, fix(fixer) { const match = comment.value.match(LETTER_PATTERN); diff --git a/tools/node_modules/eslint/lib/rules/class-methods-use-this.js b/tools/node_modules/eslint/lib/rules/class-methods-use-this.js index 774086780a..b7d94135bb 100644 --- a/tools/node_modules/eslint/lib/rules/class-methods-use-this.js +++ b/tools/node_modules/eslint/lib/rules/class-methods-use-this.js @@ -28,7 +28,11 @@ module.exports = { } }, additionalProperties: false - }] + }], + + messages: { + missingThis: "Expected 'this' to be used by class method '{{name}}'." + } }, create(context) { const config = context.options[0] ? Object.assign({}, context.options[0]) : {}; @@ -80,9 +84,9 @@ module.exports = { if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { context.report({ node, - message: "Expected 'this' to be used by class method '{{classMethod}}'.", + messageId: "missingThis", data: { - classMethod: node.parent.key.name + name: node.parent.key.name } }); } diff --git a/tools/node_modules/eslint/lib/rules/comma-dangle.js b/tools/node_modules/eslint/lib/rules/comma-dangle.js index 180979d403..50de7b59b2 100644 --- a/tools/node_modules/eslint/lib/rules/comma-dangle.js +++ b/tools/node_modules/eslint/lib/rules/comma-dangle.js @@ -124,14 +124,17 @@ module.exports = { ] } ] + }, + + messages: { + unexpected: "Unexpected trailing comma.", + missing: "Missing trailing comma." } }, create(context) { const options = normalizeOptions(context.options[0]); const sourceCode = context.getSourceCode(); - const UNEXPECTED_MESSAGE = "Unexpected trailing comma."; - const MISSING_MESSAGE = "Missing trailing comma."; /** * Gets the last item of the given node. @@ -230,7 +233,7 @@ module.exports = { context.report({ node: lastItem, loc: trailingToken.loc.start, - message: UNEXPECTED_MESSAGE, + messageId: "unexpected", fix(fixer) { return fixer.remove(trailingToken); } @@ -267,7 +270,7 @@ module.exports = { context.report({ node: lastItem, loc: trailingToken.loc.end, - message: MISSING_MESSAGE, + messageId: "missing", fix(fixer) { return fixer.insertTextAfter(trailingToken, ","); } diff --git a/tools/node_modules/eslint/lib/rules/comma-spacing.js b/tools/node_modules/eslint/lib/rules/comma-spacing.js index dd3de7f354..2a48e54d51 100644 --- a/tools/node_modules/eslint/lib/rules/comma-spacing.js +++ b/tools/node_modules/eslint/lib/rules/comma-spacing.js @@ -34,7 +34,12 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + missing: "A space is required {{loc}} ','.", + unexpected: "There should be no space {{loc}} ','." + } }, create(context) { @@ -57,17 +62,17 @@ module.exports = { /** * Reports a spacing error with an appropriate message. * @param {ASTNode} node The binary expression node to report. - * @param {string} dir Is the error "before" or "after" the comma? + * @param {string} loc Is the error "before" or "after" the comma? * @param {ASTNode} otherNode The node at the left or right of `node` * @returns {void} * @private */ - function report(node, dir, otherNode) { + function report(node, loc, otherNode) { context.report({ node, fix(fixer) { - if (options[dir]) { - if (dir === "before") { + if (options[loc]) { + if (loc === "before") { return fixer.insertTextBefore(node, " "); } return fixer.insertTextAfter(node, " "); @@ -76,7 +81,7 @@ module.exports = { let start, end; const newText = ""; - if (dir === "before") { + if (loc === "before") { start = otherNode.range[1]; end = node.range[0]; } else { @@ -87,11 +92,9 @@ module.exports = { return fixer.replaceTextRange([start, end], newText); }, - message: options[dir] - ? "A space is required {{dir}} ','." - : "There should be no space {{dir}} ','.", + messageId: options[loc] ? "missing" : "unexpected", data: { - dir + loc } }); } diff --git a/tools/node_modules/eslint/lib/rules/comma-style.js b/tools/node_modules/eslint/lib/rules/comma-style.js index 8b96e5e25a..5ba2dbb28b 100644 --- a/tools/node_modules/eslint/lib/rules/comma-style.js +++ b/tools/node_modules/eslint/lib/rules/comma-style.js @@ -36,7 +36,12 @@ module.exports = { }, additionalProperties: false } - ] + ], + messages: { + unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.", + expectedCommaFirst: "',' should be placed first.", + expectedCommaLast: "',' should be placed last." + } }, create(context) { @@ -49,7 +54,8 @@ module.exports = { FunctionDeclaration: true, FunctionExpression: true, ImportDeclaration: true, - ObjectPattern: true + ObjectPattern: true, + NewExpression: true }; if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) { @@ -134,7 +140,7 @@ module.exports = { line: commaToken.loc.end.line, column: commaToken.loc.start.column }, - message: "Bad line breaking before and after ','.", + messageId: "unexpectedLineBeforeAndAfterComma", fix: getFixerFunction("between", previousItemToken, commaToken, currentItemToken) }); @@ -142,7 +148,7 @@ module.exports = { context.report({ node: reportItem, - message: "',' should be placed first.", + messageId: "expectedCommaFirst", fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) }); @@ -154,7 +160,7 @@ module.exports = { line: commaToken.loc.end.line, column: commaToken.loc.end.column }, - message: "',' should be placed last.", + messageId: "expectedCommaLast", fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) }); } @@ -294,6 +300,11 @@ module.exports = { validateComma(node, "specifiers"); }; } + if (!exceptions.NewExpression) { + nodes.NewExpression = function(node) { + validateComma(node, "arguments"); + }; + } return nodes; } diff --git a/tools/node_modules/eslint/lib/rules/complexity.js b/tools/node_modules/eslint/lib/rules/complexity.js index 1838d4bf8e..0b86441365 100644 --- a/tools/node_modules/eslint/lib/rules/complexity.js +++ b/tools/node_modules/eslint/lib/rules/complexity.js @@ -50,7 +50,11 @@ module.exports = { } ] } - ] + ], + + messages: { + complex: "{{name}} has a complexity of {{complexity}}." + } }, create(context) { @@ -96,7 +100,7 @@ module.exports = { if (complexity > THRESHOLD) { context.report({ node, - message: "{{name}} has a complexity of {{complexity}}.", + messageId: "complex", data: { name, complexity } }); } 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 57408afb45..51334a2ab4 100644 --- a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js +++ b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js @@ -25,7 +25,15 @@ module.exports = { { enum: ["always", "never"] } - ] + ], + + messages: { + unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", + unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", + + missingSpaceBefore: "A space is required before '{{tokenValue}}'.", + missingSpaceAfter: "A space is required after '{{tokenValue}}'." + } }, create(context) { @@ -47,7 +55,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "There should be no space after '{{tokenValue}}'.", + messageId: "unexpectedSpaceAfter", data: { tokenValue: token.value }, @@ -68,7 +76,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "There should be no space before '{{tokenValue}}'.", + messageId: "unexpectedSpaceBefore", data: { tokenValue: token.value }, @@ -88,7 +96,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "A space is required after '{{tokenValue}}'.", + messageId: "missingSpaceAfter", data: { tokenValue: token.value }, @@ -108,7 +116,7 @@ module.exports = { context.report({ node, loc: token.loc.start, - message: "A space is required before '{{tokenValue}}'.", + messageId: "missingSpaceBefore", data: { tokenValue: token.value }, diff --git a/tools/node_modules/eslint/lib/rules/consistent-return.js b/tools/node_modules/eslint/lib/rules/consistent-return.js index a436dd1fc0..c86b1717ef 100644 --- a/tools/node_modules/eslint/lib/rules/consistent-return.js +++ b/tools/node_modules/eslint/lib/rules/consistent-return.js @@ -68,7 +68,13 @@ module.exports = { } }, additionalProperties: false - }] + }], + + messages: { + missingReturn: "Expected to return a value at the end of {{name}}.", + missingReturnValue: "{{name}} expected a return value.", + unexpectedReturnValue: "{{name}} expected no return value." + } }, create(context) { @@ -129,7 +135,7 @@ module.exports = { context.report({ node, loc, - message: "Expected to return a value at the end of {{name}}.", + messageId: "missingReturn", data: { name } }); } @@ -143,7 +149,7 @@ module.exports = { codePath, hasReturn: false, hasReturnValue: false, - message: "", + messageId: "", node }; }, @@ -163,17 +169,16 @@ module.exports = { if (!funcInfo.hasReturn) { funcInfo.hasReturn = true; funcInfo.hasReturnValue = hasReturnValue; - funcInfo.message = "{{name}} expected {{which}} return value."; + funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue"; funcInfo.data = { name: funcInfo.node.type === "Program" ? "Program" - : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)), - which: hasReturnValue ? "a" : "no" + : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node)) }; } else if (funcInfo.hasReturnValue !== hasReturnValue) { context.report({ node, - message: funcInfo.message, + messageId: funcInfo.messageId, data: funcInfo.data }); } diff --git a/tools/node_modules/eslint/lib/rules/consistent-this.js b/tools/node_modules/eslint/lib/rules/consistent-this.js index 60690007f2..5cc3a647da 100644 --- a/tools/node_modules/eslint/lib/rules/consistent-this.js +++ b/tools/node_modules/eslint/lib/rules/consistent-this.js @@ -24,6 +24,11 @@ module.exports = { minLength: 1 }, uniqueItems: true + }, + + messages: { + aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.", + unexpectedAlias: "Unexpected alias '{{name}}' for 'this'." } }, @@ -40,11 +45,11 @@ module.exports = { * Reports that a variable declarator or assignment expression is assigning * a non-'this' value to the specified alias. * @param {ASTNode} node - The assigning node. - * @param {string} alias - the name of the alias that was incorrectly used. + * @param {string} name - the name of the alias that was incorrectly used. * @returns {void} */ - function reportBadAssignment(node, alias) { - context.report({ node, message: "Designated alias '{{alias}}' is not assigned to 'this'.", data: { alias } }); + function reportBadAssignment(node, name) { + context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } }); } /** @@ -63,7 +68,7 @@ module.exports = { reportBadAssignment(node, name); } } else if (isThis) { - context.report({ node, message: "Unexpected alias '{{name}}' for 'this'.", data: { name } }); + context.report({ node, messageId: "unexpectedAlias", data: { name } }); } } diff --git a/tools/node_modules/eslint/lib/rules/constructor-super.js b/tools/node_modules/eslint/lib/rules/constructor-super.js index 30637472f1..3cbc2f59f8 100644 --- a/tools/node_modules/eslint/lib/rules/constructor-super.js +++ b/tools/node_modules/eslint/lib/rules/constructor-super.js @@ -99,7 +99,16 @@ module.exports = { url: "https://eslint.org/docs/rules/constructor-super" }, - schema: [] + schema: [], + + messages: { + missingSome: "Lacked a call of 'super()' in some code paths.", + missingAll: "Expected to call 'super()'.", + + duplicate: "Unexpected duplicate 'super()'.", + badSuper: "Unexpected 'super()' because 'super' is not a constructor.", + unexpected: "Unexpected 'super()'." + } }, create(context) { @@ -210,9 +219,9 @@ module.exports = { if (!calledInEveryPaths) { context.report({ - message: calledInSomePaths - ? "Lacked a call of 'super()' in some code paths." - : "Expected to call 'super()'.", + messageId: calledInSomePaths + ? "missingSome" + : "missingAll", node: node.parent }); } @@ -281,7 +290,7 @@ module.exports = { const node = nodes[i]; context.report({ - message: "Unexpected duplicate 'super()'.", + messageId: "duplicate", node }); } @@ -325,12 +334,12 @@ module.exports = { if (info) { if (duplicate) { context.report({ - message: "Unexpected duplicate 'super()'.", + messageId: "duplicate", node }); } else if (!funcInfo.superIsConstructor) { context.report({ - message: "Unexpected 'super()' because 'super' is not a constructor.", + messageId: "badSuper", node }); } else { @@ -339,7 +348,7 @@ module.exports = { } } else if (funcInfo.codePath.currentSegments.some(isReachable)) { context.report({ - message: "Unexpected 'super()'.", + messageId: "unexpected", node }); } diff --git a/tools/node_modules/eslint/lib/rules/curly.js b/tools/node_modules/eslint/lib/rules/curly.js index 7fdf57eda1..07d991b31a 100644 --- a/tools/node_modules/eslint/lib/rules/curly.js +++ b/tools/node_modules/eslint/lib/rules/curly.js @@ -51,7 +51,14 @@ module.exports = { ] }, - fixable: "code" + fixable: "code", + + messages: { + missingCurlyAfter: "Expected { after '{{name}}'.", + missingCurlyAfterCondition: "Expected { after '{{name}}' condition.", + unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.", + unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition." + } }, create(context) { @@ -130,12 +137,14 @@ module.exports = { return true; } - node = node.consequent.body[0]; - while (node) { - if (node.type === "IfStatement" && !node.alternate) { + for ( + let currentNode = node.consequent.body[0]; + currentNode; + currentNode = astUtils.getTrailingStatement(currentNode) + ) { + if (currentNode.type === "IfStatement" && !currentNode.alternate) { return true; } - node = astUtils.getTrailingStatement(node); } } @@ -143,28 +152,6 @@ module.exports = { } /** - * Reports "Expected { after ..." error - * @param {ASTNode} node The node to report. - * @param {ASTNode} bodyNode The body node that is incorrectly missing curly brackets - * @param {string} name The name to report. - * @param {string} suffix Additional string to add to the end of a report. - * @returns {void} - * @private - */ - function reportExpectedBraceError(node, bodyNode, name, suffix) { - context.report({ - node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, - message: "Expected { after '{{name}}'{{suffix}}.", - data: { - name, - suffix: (suffix ? ` ${suffix}` : "") - }, - fix: fixer => fixer.replaceText(bodyNode, `{${sourceCode.getText(bodyNode)}}`) - }); - } - - /** * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError. * @param {Token} closingBracket The } token * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block. @@ -219,61 +206,11 @@ module.exports = { } /** - * Reports "Unnecessary { after ..." error - * @param {ASTNode} node The node to report. - * @param {ASTNode} bodyNode The block statement that is incorrectly surrounded by parens - * @param {string} name The name to report. - * @param {string} suffix Additional string to add to the end of a report. - * @returns {void} - * @private - */ - function reportUnnecessaryBraceError(node, bodyNode, name, suffix) { - context.report({ - node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, - message: "Unnecessary { after '{{name}}'{{suffix}}.", - data: { - name, - suffix: (suffix ? ` ${suffix}` : "") - }, - fix(fixer) { - - /* - * `do while` expressions sometimes need a space to be inserted after `do`. - * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)` - */ - const needsPrecedingSpace = node.type === "DoWhileStatement" && - sourceCode.getTokenBefore(bodyNode).range[1] === bodyNode.range[0] && - !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(bodyNode, { skip: 1 })); - - const openingBracket = sourceCode.getFirstToken(bodyNode); - const closingBracket = sourceCode.getLastToken(bodyNode); - const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket); - - if (needsSemicolon(closingBracket)) { - - /* - * If removing braces would cause a SyntaxError due to multiple statements on the same line (or - * change the semantics of the code due to ASI), don't perform a fix. - */ - return null; - } - - const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) + - sourceCode.getText(lastTokenInBlock) + - sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]); - - return fixer.replaceText(bodyNode, (needsPrecedingSpace ? " " : "") + resultingBodyText); - } - }); - } - - /** * Prepares to check the body of a node to see if it's a block statement. * @param {ASTNode} node The node to report if there's a problem. * @param {ASTNode} body The body node to check for blocks. * @param {string} name The name to report if there's a problem. - * @param {string} suffix Additional string to add to the end of a report. + * @param {{ condition: boolean }} opts Options to pass to the report functions * @returns {Object} a prepared check object, with "actual", "expected", "check" properties. * "actual" will be `true` or `false` whether the body is already a block statement. * "expected" will be `true` or `false` if the body should be a block statement or not, or @@ -282,7 +219,7 @@ module.exports = { * "check" will be a function reporting appropriate problems depending on the other * properties. */ - function prepareCheck(node, body, name, suffix) { + function prepareCheck(node, body, name, opts) { const hasBlock = (body.type === "BlockStatement"); let expected = null; @@ -314,9 +251,53 @@ module.exports = { check() { if (this.expected !== null && this.expected !== this.actual) { if (this.expected) { - reportExpectedBraceError(node, body, name, suffix); + context.report({ + node, + loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter", + data: { + name + }, + fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`) + }); } else { - reportUnnecessaryBraceError(node, body, name, suffix); + context.report({ + node, + loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter", + data: { + name + }, + fix(fixer) { + + /* + * `do while` expressions sometimes need a space to be inserted after `do`. + * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)` + */ + const needsPrecedingSpace = node.type === "DoWhileStatement" && + sourceCode.getTokenBefore(body).range[1] === body.range[0] && + !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 })); + + const openingBracket = sourceCode.getFirstToken(body); + const closingBracket = sourceCode.getLastToken(body); + const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket); + + if (needsSemicolon(closingBracket)) { + + /* + * If removing braces would cause a SyntaxError due to multiple statements on the same line (or + * change the semantics of the code due to ASI), don't perform a fix. + */ + return null; + } + + const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) + + sourceCode.getText(lastTokenInBlock) + + sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]); + + return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText); + } + }); } } } @@ -332,14 +313,13 @@ module.exports = { function prepareIfChecks(node) { const preparedChecks = []; - do { - preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition")); - if (node.alternate && node.alternate.type !== "IfStatement") { - preparedChecks.push(prepareCheck(node, node.alternate, "else")); + for (let currentNode = node; currentNode; currentNode = currentNode.alternate) { + preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true })); + if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") { + preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else")); break; } - node = node.alternate; - } while (node); + } if (consistent) { @@ -377,7 +357,7 @@ module.exports = { }, WhileStatement(node) { - prepareCheck(node, node.body, "while", "condition").check(); + prepareCheck(node, node.body, "while", { condition: true }).check(); }, DoWhileStatement(node) { @@ -385,7 +365,7 @@ module.exports = { }, ForStatement(node) { - prepareCheck(node, node.body, "for", "condition").check(); + prepareCheck(node, node.body, "for", { condition: true }).check(); }, ForInStatement(node) { diff --git a/tools/node_modules/eslint/lib/rules/default-case.js b/tools/node_modules/eslint/lib/rules/default-case.js index a66300a99b..cf123198f4 100644 --- a/tools/node_modules/eslint/lib/rules/default-case.js +++ b/tools/node_modules/eslint/lib/rules/default-case.js @@ -27,7 +27,11 @@ module.exports = { } }, additionalProperties: false - }] + }], + + messages: { + missingDefaultCase: "Expected a default case." + } }, create(context) { @@ -82,7 +86,7 @@ module.exports = { } if (!comment || !commentPattern.test(comment.value.trim())) { - context.report({ node, message: "Expected a default case." }); + context.report({ node, messageId: "missingDefaultCase" }); } } } diff --git a/tools/node_modules/eslint/lib/rules/dot-location.js b/tools/node_modules/eslint/lib/rules/dot-location.js index c02476e035..7ff8ca65ae 100644 --- a/tools/node_modules/eslint/lib/rules/dot-location.js +++ b/tools/node_modules/eslint/lib/rules/dot-location.js @@ -26,7 +26,12 @@ module.exports = { } ], - fixable: "code" + fixable: "code", + + messages: { + expectedDotAfterObject: "Expected dot to be on same line as object.", + expectedDotBeforeProperty: "Expected dot to be on same line as property." + } }, create(context) { @@ -58,7 +63,7 @@ module.exports = { context.report({ node, loc: dot.loc.start, - message: "Expected dot to be on same line as object.", + messageId: "expectedDotAfterObject", fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${neededTextAfterObj}.${textBeforeDot}${textAfterDot}`) }); } @@ -66,7 +71,7 @@ module.exports = { context.report({ node, loc: dot.loc.start, - message: "Expected dot to be on same line as property.", + messageId: "expectedDotBeforeProperty", fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${textBeforeDot}${textAfterDot}.`) }); } diff --git a/tools/node_modules/eslint/lib/rules/dot-notation.js b/tools/node_modules/eslint/lib/rules/dot-notation.js index 10c73b41f0..c381661d41 100644 --- a/tools/node_modules/eslint/lib/rules/dot-notation.js +++ b/tools/node_modules/eslint/lib/rules/dot-notation.js @@ -41,7 +41,12 @@ module.exports = { } ], - fixable: "code" + fixable: "code", + + messages: { + useDot: "[{{key}}] is better written in dot notation.", + useBrackets: ".{{key}} is a syntax error." + } }, create(context) { @@ -71,9 +76,9 @@ module.exports = { context.report({ node: node.property, - message: "[{{propertyValue}}] is better written in dot notation.", + messageId: "useDot", data: { - propertyValue: formattedValue + key: formattedValue }, fix(fixer) { const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken); @@ -124,9 +129,9 @@ module.exports = { ) { context.report({ node: node.property, - message: ".{{propertyName}} is a syntax error.", + messageId: "useBrackets", data: { - propertyName: node.property.name + key: node.property.name }, fix(fixer) { const dot = sourceCode.getTokenBefore(node.property); diff --git a/tools/node_modules/eslint/lib/rules/eol-last.js b/tools/node_modules/eslint/lib/rules/eol-last.js index f96352676c..3ecf422739 100644 --- a/tools/node_modules/eslint/lib/rules/eol-last.js +++ b/tools/node_modules/eslint/lib/rules/eol-last.js @@ -27,7 +27,11 @@ module.exports = { { enum: ["always", "never", "unix", "windows"] } - ] + ], + messages: { + missing: "Newline required at end of file but not found.", + unexpected: "Newline not allowed at end of file." + } }, create(context) { @@ -75,7 +79,7 @@ module.exports = { context.report({ node, loc: location, - message: "Newline required at end of file but not found.", + messageId: "missing", fix(fixer) { return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF); } @@ -86,7 +90,7 @@ module.exports = { context.report({ node, loc: location, - message: "Newline not allowed at end of file.", + messageId: "unexpected", fix(fixer) { const finalEOLs = /(?:\r?\n)+$/, match = finalEOLs.exec(sourceCode.text), diff --git a/tools/node_modules/eslint/lib/rules/eqeqeq.js b/tools/node_modules/eslint/lib/rules/eqeqeq.js index fc65450af4..ec9d0a7821 100644 --- a/tools/node_modules/eslint/lib/rules/eqeqeq.js +++ b/tools/node_modules/eslint/lib/rules/eqeqeq.js @@ -56,7 +56,11 @@ module.exports = { ] }, - fixable: "code" + fixable: "code", + + messages: { + unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'." + } }, create(context) { @@ -134,7 +138,7 @@ module.exports = { context.report({ node, loc: getOperatorLocation(node), - message: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.", + messageId: "unexpected", data: { expectedOperator, actualOperator: node.operator }, fix(fixer) { diff --git a/tools/node_modules/eslint/lib/rules/generator-star-spacing.js b/tools/node_modules/eslint/lib/rules/generator-star-spacing.js index 282b37510b..68f2863626 100644 --- a/tools/node_modules/eslint/lib/rules/generator-star-spacing.js +++ b/tools/node_modules/eslint/lib/rules/generator-star-spacing.js @@ -85,7 +85,6 @@ module.exports = { } const modes = (function(option) { - option = option || {}; const defaults = optionToDefinition(option, optionDefinitions.before); return { @@ -93,7 +92,7 @@ module.exports = { anonymous: optionToDefinition(option.anonymous, defaults), method: optionToDefinition(option.method, defaults) }; - }(context.options[0])); + }(context.options[0] || {})); const sourceCode = context.getSourceCode(); diff --git a/tools/node_modules/eslint/lib/rules/guard-for-in.js b/tools/node_modules/eslint/lib/rules/guard-for-in.js index 88a9106b3f..0f85e4984a 100644 --- a/tools/node_modules/eslint/lib/rules/guard-for-in.js +++ b/tools/node_modules/eslint/lib/rules/guard-for-in.js @@ -26,16 +26,44 @@ module.exports = { return { ForInStatement(node) { + const body = node.body; - /* - * If the for-in statement has {}, then the real body is the body - * of the BlockStatement. Otherwise, just use body as provided. - */ - const body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body; + // empty statement + if (body.type === "EmptyStatement") { + return; + } + + // if statement + if (body.type === "IfStatement") { + return; + } + + // empty block + if (body.type === "BlockStatement" && body.body.length === 0) { + return; + } - if (body && body.type !== "IfStatement") { - context.report({ node, message: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype." }); + // block with just if statement + if (body.type === "BlockStatement" && body.body.length === 1 && body.body[0].type === "IfStatement") { + return; } + + // block that starts with if statement + if (body.type === "BlockStatement" && body.body.length >= 1 && body.body[0].type === "IfStatement") { + const i = body.body[0]; + + // ... whose consequent is a continue + if (i.consequent.type === "ContinueStatement") { + return; + } + + // ... whose consequent is a block that contains only a continue + if (i.consequent.type === "BlockStatement" && i.consequent.body.length === 1 && i.consequent.body[0].type === "ContinueStatement") { + return; + } + } + + context.report({ node, message: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype." }); } }; diff --git a/tools/node_modules/eslint/lib/rules/indent-legacy.js b/tools/node_modules/eslint/lib/rules/indent-legacy.js index 701cf01632..dc6d1689e4 100644 --- a/tools/node_modules/eslint/lib/rules/indent-legacy.js +++ b/tools/node_modules/eslint/lib/rules/indent-legacy.js @@ -505,12 +505,9 @@ module.exports = { */ function getParentNodeByType(node, type, stopAtList) { let parent = node.parent; + const stopAtSet = new Set(stopAtList || ["Program"]); - if (!stopAtList) { - stopAtList = ["Program"]; - } - - while (parent.type !== type && stopAtList.indexOf(parent.type) === -1 && parent.type !== "Program") { + while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") { parent = parent.parent; } @@ -941,19 +938,19 @@ module.exports = { /** * Returns the expected indentation for the case statement * @param {ASTNode} node node to examine - * @param {int} [switchIndent] indent for switch statement + * @param {int} [providedSwitchIndent] indent for switch statement * @returns {int} indent size */ - function expectedCaseIndent(node, switchIndent) { + function expectedCaseIndent(node, providedSwitchIndent) { const switchNode = (node.type === "SwitchStatement") ? node : node.parent; + const switchIndent = typeof providedSwitchIndent === "undefined" + ? getNodeIndent(switchNode).goodChar + : providedSwitchIndent; let caseIndent; if (caseIndentStore[switchNode.loc.start.line]) { return caseIndentStore[switchNode.loc.start.line]; } - if (typeof switchIndent === "undefined") { - switchIndent = getNodeIndent(switchNode).goodChar; - } if (switchNode.cases.length > 0 && options.SwitchCase === 0) { caseIndent = switchIndent; diff --git a/tools/node_modules/eslint/lib/rules/indent.js b/tools/node_modules/eslint/lib/rules/indent.js index 2159880f88..a08b4d7e5f 100644 --- a/tools/node_modules/eslint/lib/rules/indent.js +++ b/tools/node_modules/eslint/lib/rules/indent.js @@ -442,6 +442,7 @@ class OffsetStorage { const offset = ( offsetInfo.from && offsetInfo.from.loc.start.line === token.loc.start.line && + !/^\s*?\n/.test(token.value) && !offsetInfo.force ) ? 0 : offsetInfo.offset * this._indentSize; @@ -779,6 +780,19 @@ module.exports = { } /** + * Counts the number of linebreaks that follow the last non-whitespace character in a string + * @param {string} string The string to check + * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character, + * or the total number of linebreaks if the string is all whitespace. + */ + function countTrailingLinebreaks(string) { + const trailingWhitespace = string.match(/\s*$/)[0]; + const linebreakMatches = trailingWhitespace.match(astUtils.createGlobalLinebreakMatcher()); + + return linebreakMatches === null ? 0 : linebreakMatches.length; + } + + /** * Check indentation for lists of elements (arrays, objects, function params) * @param {ASTNode[]} elements List of elements that should be offset * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '[' @@ -835,8 +849,12 @@ module.exports = { } else { const previousElement = elements[index - 1]; const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement); + const previousElementLastToken = previousElement && sourceCode.getLastToken(previousElement); - if (previousElement && sourceCode.getLastToken(previousElement).loc.start.line > startToken.loc.end.line) { + if ( + previousElement && + previousElementLastToken.loc.end.line - countTrailingLinebreaks(previousElementLastToken.value) > startToken.loc.end.line + ) { offsets.setDesiredOffsets(element.range, firstTokenOfPreviousElement, 0); } } @@ -979,6 +997,8 @@ module.exports = { return !node || node.loc.start.line === token.loc.start.line; } + const ignoredNodeFirstTokens = new Set(); + const baseOffsetListeners = { "ArrayExpression, ArrayPattern"(node) { const openingBracket = sourceCode.getFirstToken(node); @@ -1009,15 +1029,6 @@ module.exports = { addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters); } addBlocklessNodeIndent(node.body); - - let arrowToken; - - if (node.params.length) { - arrowToken = sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isArrowToken); - } else { - arrowToken = sourceCode.getFirstToken(node, astUtils.isArrowToken); - } - offsets.setDesiredOffset(arrowToken, sourceCode.getFirstToken(node), 0); }, AssignmentExpression(node) { @@ -1042,7 +1053,6 @@ module.exports = { offsets.ignoreToken(operator); offsets.ignoreToken(tokenAfterOperator); offsets.setDesiredOffset(tokenAfterOperator, operator, 0); - offsets.setDesiredOffsets([tokenAfterOperator.range[1], node.range[1]], tokenAfterOperator, 1); }, "BlockStatement, ClassBody"(node) { @@ -1094,8 +1104,8 @@ module.exports = { const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?"); const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":"); - const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken, { includeComments: true }); - const lastConsequentToken = sourceCode.getTokenBefore(colonToken, { includeComments: true }); + const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken); + const lastConsequentToken = sourceCode.getTokenBefore(colonToken); const firstAlternateToken = sourceCode.getTokenAfter(colonToken); offsets.setDesiredOffset(questionMarkToken, firstToken, 1); @@ -1128,9 +1138,6 @@ module.exports = { */ offsets.setDesiredOffset(firstAlternateToken, firstToken, 1); } - - offsets.setDesiredOffsets([questionMarkToken.range[1], colonToken.range[0]], firstConsequentToken, 0); - offsets.setDesiredOffsets([colonToken.range[1], node.range[1]], firstAlternateToken, 0); } }, @@ -1272,20 +1279,9 @@ module.exports = { SwitchStatement(node) { const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken); const closingCurly = sourceCode.getLastToken(node); - const caseKeywords = node.cases.map(switchCase => sourceCode.getFirstToken(switchCase)); offsets.setDesiredOffsets([openingCurly.range[1], closingCurly.range[0]], openingCurly, options.SwitchCase); - node.cases.forEach((switchCase, index) => { - const caseKeyword = caseKeywords[index]; - - if (!(switchCase.consequent.length === 1 && switchCase.consequent[0].type === "BlockStatement")) { - const tokenAfterCurrentCase = index === node.cases.length - 1 ? closingCurly : caseKeywords[index + 1]; - - offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1); - } - }); - if (node.cases.length) { sourceCode.getTokensBetween( node.cases[node.cases.length - 1], @@ -1295,6 +1291,15 @@ module.exports = { } }, + SwitchCase(node) { + if (!(node.consequent.length === 1 && node.consequent[0].type === "BlockStatement")) { + const caseKeyword = sourceCode.getFirstToken(node); + const tokenAfterCurrentCase = sourceCode.getTokenAfter(node); + + offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1); + } + }, + TemplateLiteral(node) { node.expressions.forEach((expression, index) => { const previousQuasi = node.quasis[index]; @@ -1385,7 +1390,6 @@ module.exports = { const firstToken = sourceCode.getFirstToken(node); offsets.setDesiredOffsets(node.name.range, firstToken, 1); - offsets.setDesiredOffset(sourceCode.getLastToken(node), firstToken, 0); }, JSXExpressionContainer(node) { @@ -1397,7 +1401,15 @@ module.exports = { openingCurly, 1 ); - offsets.setDesiredOffset(closingCurly, openingCurly, 0); + }, + + "*"(node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } } }; @@ -1406,7 +1418,8 @@ module.exports = { /* * To ignore the indentation of a node: * 1. Don't call the node's listener when entering it (if it has a listener) - * 2. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. */ const offsetListeners = lodash.mapValues( baseOffsetListeners, @@ -1434,7 +1447,16 @@ module.exports = { // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. const ignoredNodes = new Set(); - const addToIgnoredNodes = ignoredNodes.add.bind(ignoredNodes); + + /** + * Ignores a node + * @param {ASTNode} node The node to ignore + * @returns {void} + */ + function addToIgnoredNodes(node) { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } const ignoredNodeListeners = options.ignoredNodes.reduce( (listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), @@ -1457,7 +1479,7 @@ module.exports = { // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. if (!KNOWN_NODES.has(node.type)) { - ignoredNodes.add(node); + addToIgnoredNodes(node); } }, "Program:exit"() { diff --git a/tools/node_modules/eslint/lib/rules/key-spacing.js b/tools/node_modules/eslint/lib/rules/key-spacing.js index 1ae4990a59..b1208e19e9 100644 --- a/tools/node_modules/eslint/lib/rules/key-spacing.js +++ b/tools/node_modules/eslint/lib/rules/key-spacing.js @@ -360,9 +360,10 @@ module.exports = { */ function isKeyValueProperty(property) { return !( - (property.method || + property.method || property.shorthand || - property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadProperty" + property.kind !== "init" || + property.type !== "Property" // Could be "ExperimentalSpreadProperty" or "SpreadElement" ); } diff --git a/tools/node_modules/eslint/lib/rules/keyword-spacing.js b/tools/node_modules/eslint/lib/rules/keyword-spacing.js index d0dd640255..a2ce79ab5a 100644 --- a/tools/node_modules/eslint/lib/rules/keyword-spacing.js +++ b/tools/node_modules/eslint/lib/rules/keyword-spacing.js @@ -108,13 +108,10 @@ module.exports = { * Reports a given token if there are not space(s) before the token. * * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the previous - * token to check. + * @param {RegExp} pattern - A pattern of the previous token to check. * @returns {void} */ function expectSpaceBefore(token, pattern) { - pattern = pattern || PREV_TOKEN; - const prevToken = sourceCode.getTokenBefore(token); if (prevToken && @@ -138,13 +135,10 @@ module.exports = { * Reports a given token if there are space(s) before the token. * * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the previous - * token to check. + * @param {RegExp} pattern - A pattern of the previous token to check. * @returns {void} */ function unexpectSpaceBefore(token, pattern) { - pattern = pattern || PREV_TOKEN; - const prevToken = sourceCode.getTokenBefore(token); if (prevToken && @@ -168,13 +162,10 @@ module.exports = { * Reports a given token if there are not space(s) after the token. * * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the next - * token to check. + * @param {RegExp} pattern - A pattern of the next token to check. * @returns {void} */ function expectSpaceAfter(token, pattern) { - pattern = pattern || NEXT_TOKEN; - const nextToken = sourceCode.getTokenAfter(token); if (nextToken && @@ -198,13 +189,10 @@ module.exports = { * Reports a given token if there are space(s) after the token. * * @param {Token} token - A token to report. - * @param {RegExp|undefined} pattern - Optional. A pattern of the next - * token to check. + * @param {RegExp} pattern - A pattern of the next token to check. * @returns {void} */ function unexpectSpaceAfter(token, pattern) { - pattern = pattern || NEXT_TOKEN; - const nextToken = sourceCode.getTokenAfter(token); if (nextToken && @@ -274,7 +262,7 @@ module.exports = { * @returns {void} */ function checkSpacingBefore(token, pattern) { - checkMethodMap[token.value].before(token, pattern); + checkMethodMap[token.value].before(token, pattern || PREV_TOKEN); } /** @@ -287,7 +275,7 @@ module.exports = { * @returns {void} */ function checkSpacingAfter(token, pattern) { - checkMethodMap[token.value].after(token, pattern); + checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN); } /** @@ -436,7 +424,12 @@ module.exports = { * @returns {void} */ function checkSpacingForForOfStatement(node) { - checkSpacingAroundFirstToken(node); + if (node.await) { + checkSpacingBefore(sourceCode.getFirstToken(node, 0)); + checkSpacingAfter(sourceCode.getFirstToken(node, 1)); + } else { + checkSpacingAroundFirstToken(node); + } checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken)); } diff --git a/tools/node_modules/eslint/lib/rules/max-len.js b/tools/node_modules/eslint/lib/rules/max-len.js index 35ac803b65..273eb84907 100644 --- a/tools/node_modules/eslint/lib/rules/max-len.js +++ b/tools/node_modules/eslint/lib/rules/max-len.js @@ -213,7 +213,8 @@ module.exports = { * @returns {ASTNode[]} An array of string nodes. */ function getAllStrings() { - return sourceCode.ast.tokens.filter(token => token.type === "String"); + return sourceCode.ast.tokens.filter(token => (token.type === "String" || + (token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute"))); } /** @@ -287,6 +288,7 @@ module.exports = { * line is a comment */ let lineIsComment = false; + let textToMeasure; /* * We can short-circuit the comment checks if we're already out of @@ -305,12 +307,17 @@ module.exports = { if (isFullLineComment(line, lineNumber, comment)) { lineIsComment = true; + textToMeasure = line; } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { - line = stripTrailingComment(line, comment); + textToMeasure = stripTrailingComment(line, comment); + } else { + textToMeasure = line; } + } else { + textToMeasure = line; } - if (ignorePattern && ignorePattern.test(line) || - ignoreUrls && URL_REGEXP.test(line) || + if (ignorePattern && ignorePattern.test(textToMeasure) || + ignoreUrls && URL_REGEXP.test(textToMeasure) || ignoreStrings && stringsByLine[lineNumber] || ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] || ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber] @@ -320,7 +327,7 @@ module.exports = { return; } - const lineLength = computeLineLength(line, tabWidth); + const lineLength = computeLineLength(textToMeasure, tabWidth); const commentLengthApplies = lineIsComment && maxCommentLength; if (lineIsComment && ignoreComments) { diff --git a/tools/node_modules/eslint/lib/rules/no-alert.js b/tools/node_modules/eslint/lib/rules/no-alert.js index b4fd231215..69d419da1c 100644 --- a/tools/node_modules/eslint/lib/rules/no-alert.js +++ b/tools/node_modules/eslint/lib/rules/no-alert.js @@ -24,17 +24,6 @@ function isProhibitedIdentifier(name) { } /** - * Reports the given node and identifier name. - * @param {RuleContext} context The ESLint rule context. - * @param {ASTNode} node The node to report on. - * @param {string} identifierName The name of the identifier. - * @returns {void} - */ -function report(context, node, identifierName) { - context.report(node, "Unexpected {{name}}.", { name: identifierName }); -} - -/** * Finds the eslint-scope reference in the given scope. * @param {Object} scope The scope to search. * @param {ASTNode} node The identifier node. @@ -92,7 +81,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-alert" }, - schema: [] + schema: [], + + messages: { + unexpected: "Unexpected {{name}}." + } }, create(context) { @@ -103,17 +96,25 @@ module.exports = { // without window. if (callee.type === "Identifier") { - const identifierName = callee.name; + const name = callee.name; if (!isShadowed(currentScope, callee) && isProhibitedIdentifier(callee.name)) { - report(context, node, identifierName); + context.report({ + node, + messageId: "unexpected", + data: { name } + }); } } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, callee.object)) { - const identifierName = getPropertyName(callee); - - if (isProhibitedIdentifier(identifierName)) { - report(context, node, identifierName); + const name = getPropertyName(callee); + + if (isProhibitedIdentifier(name)) { + context.report({ + node, + messageId: "unexpected", + data: { name } + }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-array-constructor.js b/tools/node_modules/eslint/lib/rules/no-array-constructor.js index 187389fdff..51676f7821 100644 --- a/tools/node_modules/eslint/lib/rules/no-array-constructor.js +++ b/tools/node_modules/eslint/lib/rules/no-array-constructor.js @@ -18,7 +18,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-array-constructor" }, - schema: [] + schema: [], + + messages: { + preferLiteral: "The array literal notation [] is preferable." + } }, create(context) { @@ -35,7 +39,7 @@ module.exports = { node.callee.type === "Identifier" && node.callee.name === "Array" ) { - context.report({ node, message: "The array literal notation [] is preferrable." }); + context.report({ node, messageId: "preferLiteral" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-await-in-loop.js b/tools/node_modules/eslint/lib/rules/no-await-in-loop.js index 7d4f8a0a6b..ef0bda90bf 100644 --- a/tools/node_modules/eslint/lib/rules/no-await-in-loop.js +++ b/tools/node_modules/eslint/lib/rules/no-await-in-loop.js @@ -4,24 +4,54 @@ */ "use strict"; -// Node types which are considered loops. -const loopTypes = new Set([ - "ForStatement", - "ForOfStatement", - "ForInStatement", - "WhileStatement", - "DoWhileStatement" -]); +/** + * Check whether it should stop traversing ancestors at the given node. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if it should stop traversing. + */ +function isBoundary(node) { + const t = node.type; + + return ( + t === "FunctionDeclaration" || + t === "FunctionExpression" || + t === "ArrowFunctionExpression" || + + /* + * Don't report the await expressions on for-await-of loop since it's + * asynchronous iteration intentionally. + */ + (t === "ForOfStatement" && node.await === true) + ); +} -/* - * Node types at which we should stop looking for loops. For example, it is fine to declare an async - * function within a loop, and use await inside of that. +/** + * Check whether the given node is in loop. + * @param {ASTNode} node A node to check. + * @param {ASTNode} parent A parent node to check. + * @returns {boolean} `true` if the node is in loop. */ -const boundaryTypes = new Set([ - "FunctionDeclaration", - "FunctionExpression", - "ArrowFunctionExpression" -]); +function isLooped(node, parent) { + switch (parent.type) { + case "ForStatement": + return ( + node === parent.test || + node === parent.update || + node === parent.body + ); + + case "ForOfStatement": + case "ForInStatement": + return node === parent.body; + + case "WhileStatement": + case "DoWhileStatement": + return node === parent.test || node === parent.body; + + default: + return false; + } +} module.exports = { meta: { @@ -31,54 +61,42 @@ module.exports = { recommended: false, url: "https://eslint.org/docs/rules/no-await-in-loop" }, - schema: [] + schema: [], + messages: { + unexpectedAwait: "Unexpected `await` inside a loop." + } }, create(context) { - return { - AwaitExpression(node) { - const ancestors = context.getAncestors(); - - // Reverse so that we can traverse from the deepest node upwards. - ancestors.reverse(); - - /* - * Create a set of all the ancestors plus this node so that we can check - * if this use of await appears in the body of the loop as opposed to - * the right-hand side of a for...of, for example. - */ - const ancestorSet = new Set(ancestors).add(node); - for (let i = 0; i < ancestors.length; i++) { - const ancestor = ancestors[i]; - - if (boundaryTypes.has(ancestor.type)) { + /** + * Validate an await expression. + * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate. + * @returns {void} + */ + function validate(awaitNode) { + if (awaitNode.type === "ForOfStatement" && !awaitNode.await) { + return; + } - /* - * Short-circuit out if we encounter a boundary type. Loops above - * this do not matter. - */ - return; - } - if (loopTypes.has(ancestor.type)) { + let node = awaitNode; + let parent = node.parent; - /* - * Only report if we are actually in the body or another part that gets executed on - * every iteration. - */ - if ( - ancestorSet.has(ancestor.body) || - ancestorSet.has(ancestor.test) || - ancestorSet.has(ancestor.update) - ) { - context.report({ - node, - message: "Unexpected `await` inside a loop." - }); - return; - } - } + while (parent && !isBoundary(parent)) { + if (isLooped(node, parent)) { + context.report({ + node: awaitNode, + messageId: "unexpectedAwait" + }); + return; } + node = parent; + parent = parent.parent; } + } + + return { + AwaitExpression: validate, + ForOfStatement: validate }; } }; diff --git a/tools/node_modules/eslint/lib/rules/no-bitwise.js b/tools/node_modules/eslint/lib/rules/no-bitwise.js index 8376674f65..36bbdaf349 100644 --- a/tools/node_modules/eslint/lib/rules/no-bitwise.js +++ b/tools/node_modules/eslint/lib/rules/no-bitwise.js @@ -46,7 +46,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpected: "Unexpected use of '{{operator}}'." + } }, create(context) { @@ -60,7 +64,7 @@ module.exports = { * @returns {void} */ function report(node) { - context.report({ node, message: "Unexpected use of '{{operator}}'.", data: { operator: node.operator } }); + context.report({ node, messageId: "unexpected", data: { operator: node.operator } }); } /** diff --git a/tools/node_modules/eslint/lib/rules/no-buffer-constructor.js b/tools/node_modules/eslint/lib/rules/no-buffer-constructor.js index 55f181ee69..51f78edb1f 100644 --- a/tools/node_modules/eslint/lib/rules/no-buffer-constructor.js +++ b/tools/node_modules/eslint/lib/rules/no-buffer-constructor.js @@ -11,12 +11,15 @@ module.exports = { meta: { docs: { - description: "disallow use of the Buffer() constructor", + description: "disallow use of the `Buffer()` constructor", category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-buffer-constructor" }, - schema: [] + schema: [], + messages: { + deprecated: "{{expr}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead." + } }, create(context) { @@ -29,8 +32,8 @@ module.exports = { "CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"(node) { context.report({ node, - message: "{{example}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.", - data: { example: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" } + messageId: "deprecated", + data: { expr: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" } }); } }; diff --git a/tools/node_modules/eslint/lib/rules/no-caller.js b/tools/node_modules/eslint/lib/rules/no-caller.js index df10bf3736..9756b212ff 100644 --- a/tools/node_modules/eslint/lib/rules/no-caller.js +++ b/tools/node_modules/eslint/lib/rules/no-caller.js @@ -18,7 +18,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-caller" }, - schema: [] + schema: [], + + messages: { + unexpected: "Avoid arguments.{{prop}}." + } }, create(context) { @@ -30,7 +34,7 @@ module.exports = { propertyName = node.property.name; if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { - context.report({ node, message: "Avoid arguments.{{property}}.", data: { property: propertyName } }); + context.report({ node, messageId: "unexpected", data: { prop: propertyName } }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-case-declarations.js b/tools/node_modules/eslint/lib/rules/no-case-declarations.js index 03c730ddbd..862be4c57d 100644 --- a/tools/node_modules/eslint/lib/rules/no-case-declarations.js +++ b/tools/node_modules/eslint/lib/rules/no-case-declarations.js @@ -17,7 +17,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-case-declarations" }, - schema: [] + schema: [], + + messages: { + unexpected: "Unexpected lexical declaration in case block." + } }, create(context) { @@ -47,7 +51,7 @@ module.exports = { if (isLexicalDeclaration(statement)) { context.report({ node, - message: "Unexpected lexical declaration in case block." + messageId: "unexpected" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-catch-shadow.js b/tools/node_modules/eslint/lib/rules/no-catch-shadow.js index 69a4e6aff8..907792278f 100644 --- a/tools/node_modules/eslint/lib/rules/no-catch-shadow.js +++ b/tools/node_modules/eslint/lib/rules/no-catch-shadow.js @@ -24,7 +24,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-catch-shadow" }, - schema: [] + schema: [], + + messages: { + mutable: "Value of '{{name}}' may be overwritten in IE 8 and earlier." + } }, create(context) { @@ -61,7 +65,7 @@ module.exports = { } if (paramIsShadowing(scope, node.param.name)) { - context.report({ node, message: "Value of '{{name}}' may be overwritten in IE 8 and earlier.", data: { name: node.param.name } }); + context.report({ node, messageId: "mutable", data: { name: node.param.name } }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-class-assign.js b/tools/node_modules/eslint/lib/rules/no-class-assign.js index 56e75122a2..58dddd6a68 100644 --- a/tools/node_modules/eslint/lib/rules/no-class-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-class-assign.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-class-assign" }, - schema: [] + schema: [], + + messages: { + class: "'{{name}}' is a class." + } }, create(context) { @@ -32,7 +36,7 @@ module.exports = { */ function checkVariable(variable) { astUtils.getModifyingReferences(variable.references).forEach(reference => { - context.report({ node: reference.identifier, message: "'{{name}}' is a class.", data: { name: reference.identifier.name } }); + context.report({ node: reference.identifier, messageId: "class", data: { name: reference.identifier.name } }); }); } diff --git a/tools/node_modules/eslint/lib/rules/no-compare-neg-zero.js b/tools/node_modules/eslint/lib/rules/no-compare-neg-zero.js index 09cf295063..6903bd0654 100644 --- a/tools/node_modules/eslint/lib/rules/no-compare-neg-zero.js +++ b/tools/node_modules/eslint/lib/rules/no-compare-neg-zero.js @@ -17,7 +17,10 @@ module.exports = { url: "https://eslint.org/docs/rules/no-compare-neg-zero" }, fixable: null, - schema: [] + schema: [], + messages: { + unexpected: "Do not use the '{{operator}}' operator to compare against -0." + } }, create(context) { @@ -43,7 +46,7 @@ module.exports = { if (isNegZero(node.left) || isNegZero(node.right)) { context.report({ node, - message: "Do not use the '{{operator}}' operator to compare against -0.", + messageId: "unexpected", data: { operator: node.operator } }); } 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 e761be14ec..f949bcc884 100644 --- a/tools/node_modules/eslint/lib/rules/no-cond-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-cond-assign.js @@ -30,7 +30,14 @@ module.exports = { { enum: ["except-parens", "always"] } - ] + ], + + messages: { + unexpected: "Unexpected assignment within {{type}}.", + + // must match JSHint's error message + missing: "Expected a conditional expression and instead saw an assignment." + } }, create(context) { @@ -95,11 +102,10 @@ module.exports = { ) ) { - // must match JSHint's error message context.report({ node, loc: node.test.loc.start, - message: "Expected a conditional expression and instead saw an assignment." + messageId: "missing" }); } } @@ -115,7 +121,7 @@ module.exports = { if (ancestor) { context.report({ node: ancestor, - message: "Unexpected assignment within {{type}}.", + messageId: "unexpected", data: { type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type } diff --git a/tools/node_modules/eslint/lib/rules/no-confusing-arrow.js b/tools/node_modules/eslint/lib/rules/no-confusing-arrow.js index 542a4060e2..297e3b1480 100644 --- a/tools/node_modules/eslint/lib/rules/no-confusing-arrow.js +++ b/tools/node_modules/eslint/lib/rules/no-confusing-arrow.js @@ -42,7 +42,11 @@ module.exports = { allowParens: { type: "boolean" } }, additionalProperties: false - }] + }], + + messages: { + confusing: "Arrow function used ambiguously with a conditional expression." + } }, create(context) { @@ -60,7 +64,7 @@ module.exports = { if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) { context.report({ node, - message: "Arrow function used ambiguously with a conditional expression.", + messageId: "confusing", fix(fixer) { // if `allowParens` is not set to true dont bother wrapping in parens diff --git a/tools/node_modules/eslint/lib/rules/no-console.js b/tools/node_modules/eslint/lib/rules/no-console.js index b00582ebad..fd5c33a8e7 100644 --- a/tools/node_modules/eslint/lib/rules/no-console.js +++ b/tools/node_modules/eslint/lib/rules/no-console.js @@ -39,7 +39,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpected: "Unexpected console statement." + } }, create(context) { @@ -102,7 +106,7 @@ module.exports = { context.report({ node, loc: node.loc, - message: "Unexpected console statement." + messageId: "unexpected" }); } diff --git a/tools/node_modules/eslint/lib/rules/no-const-assign.js b/tools/node_modules/eslint/lib/rules/no-const-assign.js index 8a08a52df4..043fe05df7 100644 --- a/tools/node_modules/eslint/lib/rules/no-const-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-const-assign.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-const-assign" }, - schema: [] + schema: [], + + messages: { + const: "'{{name}}' is constant." + } }, create(context) { @@ -32,7 +36,7 @@ module.exports = { */ function checkVariable(variable) { astUtils.getModifyingReferences(variable.references).forEach(reference => { - context.report({ node: reference.identifier, message: "'{{name}}' is constant.", data: { name: reference.identifier.name } }); + context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } }); }); } diff --git a/tools/node_modules/eslint/lib/rules/no-constant-condition.js b/tools/node_modules/eslint/lib/rules/no-constant-condition.js index 5611d617ec..724da97412 100644 --- a/tools/node_modules/eslint/lib/rules/no-constant-condition.js +++ b/tools/node_modules/eslint/lib/rules/no-constant-condition.js @@ -28,8 +28,11 @@ module.exports = { }, additionalProperties: false } + ], - ] + messages: { + unexpected: "Unexpected constant condition." + } }, create(context) { @@ -139,7 +142,7 @@ module.exports = { function checkConstantConditionLoopInSet(node) { if (loopsInCurrentScope.has(node)) { loopsInCurrentScope.delete(node); - context.report({ node: node.test, message: "Unexpected constant condition." }); + context.report({ node: node.test, messageId: "unexpected" }); } } @@ -151,7 +154,7 @@ module.exports = { */ function reportIfConstant(node) { if (node.test && isConstant(node.test, true)) { - context.report({ node: node.test, message: "Unexpected constant condition." }); + context.report({ node: node.test, messageId: "unexpected" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-continue.js b/tools/node_modules/eslint/lib/rules/no-continue.js index 52061ef532..3075b77f9f 100644 --- a/tools/node_modules/eslint/lib/rules/no-continue.js +++ b/tools/node_modules/eslint/lib/rules/no-continue.js @@ -18,14 +18,18 @@ module.exports = { url: "https://eslint.org/docs/rules/no-continue" }, - schema: [] + schema: [], + + messages: { + unexpected: "Unexpected use of continue statement." + } }, create(context) { return { ContinueStatement(node) { - context.report({ node, message: "Unexpected use of continue statement." }); + context.report({ node, messageId: "unexpected" }); } }; diff --git a/tools/node_modules/eslint/lib/rules/no-control-regex.js b/tools/node_modules/eslint/lib/rules/no-control-regex.js index 676c666128..24bb6be667 100644 --- a/tools/node_modules/eslint/lib/rules/no-control-regex.js +++ b/tools/node_modules/eslint/lib/rules/no-control-regex.js @@ -5,6 +5,44 @@ "use strict"; +const RegExpValidator = require("regexpp").RegExpValidator; +const collector = new class { + constructor() { + this.ecmaVersion = 2018; + this._source = ""; + this._controlChars = []; + this._validator = new RegExpValidator(this); + } + + onPatternEnter() { + this._controlChars = []; + } + + onCharacter(start, end, cp) { + if (cp >= 0x00 && + cp <= 0x1F && + ( + this._source.codePointAt(start) === cp || + this._source.slice(start, end).startsWith("\\x") || + this._source.slice(start, end).startsWith("\\u") + ) + ) { + this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`); + } + } + + collectControlChars(regexpStr) { + try { + this._source = regexpStr; + this._validator.validatePattern(regexpStr); // Call onCharacter hook + } catch (err) { + + // Ignore syntax errors in RegExp. + } + return this._controlChars; + } +}(); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -18,7 +56,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-control-regex" }, - schema: [] + schema: [], + + messages: { + unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}." + } }, create(context) { @@ -26,95 +68,36 @@ module.exports = { /** * Get the regex expression * @param {ASTNode} node node to evaluate - * @returns {*} Regex if found else null + * @returns {RegExp|null} Regex if found else null * @private */ - function getRegExp(node) { - if (node.value instanceof RegExp) { - return node.value; + function getRegExpPattern(node) { + if (node.regex) { + return node.regex.pattern; } - if (typeof node.value === "string") { - - const parent = context.getAncestors().pop(); - - if ((parent.type === "NewExpression" || parent.type === "CallExpression") && - parent.callee.type === "Identifier" && parent.callee.name === "RegExp" - ) { - - // there could be an invalid regular expression string - try { - return new RegExp(node.value); - } catch (ex) { - return null; - } - } + if (typeof node.value === "string" && + (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") && + node.parent.callee.type === "Identifier" && + node.parent.callee.name === "RegExp" && + node.parent.arguments[0] === node + ) { + return node.value; } return null; } - - const controlChar = /[\x00-\x1f]/g; // eslint-disable-line no-control-regex - const consecutiveSlashes = /\\+/g; - const consecutiveSlashesAtEnd = /\\+$/g; - const stringControlChar = /\\x[01][0-9a-f]/ig; - const stringControlCharWithoutSlash = /x[01][0-9a-f]/ig; - - /** - * Return a list of the control characters in the given regex string - * @param {string} regexStr regex as string to check - * @returns {array} returns a list of found control characters on given string - * @private - */ - function getControlCharacters(regexStr) { - - // check control characters, if RegExp object used - const controlChars = regexStr.match(controlChar) || []; - - let stringControlChars = []; - - // check substr, if regex literal used - const subStrIndex = regexStr.search(stringControlChar); - - if (subStrIndex > -1) { - - // is it escaped, check backslash count - const possibleEscapeCharacters = regexStr.slice(0, subStrIndex).match(consecutiveSlashesAtEnd); - - const hasControlChars = possibleEscapeCharacters === null || !(possibleEscapeCharacters[0].length % 2); - - if (hasControlChars) { - stringControlChars = regexStr.slice(subStrIndex, -1) - .split(consecutiveSlashes) - .filter(Boolean) - .map(x => { - const match = x.match(stringControlCharWithoutSlash) || [x]; - - return `\\${match[0]}`; - }); - } - } - - return controlChars.map(x => { - const hexCode = `0${x.charCodeAt(0).toString(16)}`.slice(-2); - - return `\\x${hexCode}`; - }).concat(stringControlChars); - } - return { Literal(node) { - const regex = getRegExp(node); - - if (regex) { - const computedValue = regex.toString(); + const pattern = getRegExpPattern(node); - const controlCharacters = getControlCharacters(computedValue); + if (pattern) { + const controlCharacters = collector.collectControlChars(pattern); if (controlCharacters.length > 0) { context.report({ node, - message: "Unexpected control character(s) in regular expression: {{controlChars}}.", + messageId: "unexpected", data: { controlChars: controlCharacters.join(", ") } diff --git a/tools/node_modules/eslint/lib/rules/no-debugger.js b/tools/node_modules/eslint/lib/rules/no-debugger.js index 7d816e3a00..f00e819df0 100644 --- a/tools/node_modules/eslint/lib/rules/no-debugger.js +++ b/tools/node_modules/eslint/lib/rules/no-debugger.js @@ -20,7 +20,10 @@ module.exports = { url: "https://eslint.org/docs/rules/no-debugger" }, fixable: "code", - schema: [] + schema: [], + messages: { + unexpected: "Unexpected 'debugger' statement." + } }, create(context) { @@ -29,7 +32,7 @@ module.exports = { DebuggerStatement(node) { context.report({ node, - message: "Unexpected 'debugger' statement.", + messageId: "unexpected", fix(fixer) { if (astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { return fixer.remove(node); diff --git a/tools/node_modules/eslint/lib/rules/no-delete-var.js b/tools/node_modules/eslint/lib/rules/no-delete-var.js index 9ca09f1dea..f54a396ec2 100644 --- a/tools/node_modules/eslint/lib/rules/no-delete-var.js +++ b/tools/node_modules/eslint/lib/rules/no-delete-var.js @@ -18,7 +18,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-delete-var" }, - schema: [] + schema: [], + + messages: { + unexpected: "Variables should not be deleted." + } }, create(context) { @@ -27,7 +31,7 @@ module.exports = { UnaryExpression(node) { if (node.operator === "delete" && node.argument.type === "Identifier") { - context.report({ node, message: "Variables should not be deleted." }); + context.report({ node, messageId: "unexpected" }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-div-regex.js b/tools/node_modules/eslint/lib/rules/no-div-regex.js index 87423f8861..c050249fd6 100644 --- a/tools/node_modules/eslint/lib/rules/no-div-regex.js +++ b/tools/node_modules/eslint/lib/rules/no-div-regex.js @@ -18,7 +18,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-div-regex" }, - schema: [] + schema: [], + + messages: { + unexpected: "A regular expression literal can be confused with '/='." + } }, create(context) { @@ -30,7 +34,7 @@ module.exports = { const token = sourceCode.getFirstToken(node); if (token.type === "RegularExpression" && token.value[1] === "=") { - context.report({ node, message: "A regular expression literal can be confused with '/='." }); + context.report({ node, messageId: "unexpected" }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-args.js b/tools/node_modules/eslint/lib/rules/no-dupe-args.js index a71d01f9bd..e5a7f4154e 100644 --- a/tools/node_modules/eslint/lib/rules/no-dupe-args.js +++ b/tools/node_modules/eslint/lib/rules/no-dupe-args.js @@ -18,7 +18,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-dupe-args" }, - schema: [] + schema: [], + + messages: { + unexpected: "Duplicate param '{{name}}'." + } }, create(context) { @@ -54,7 +58,7 @@ module.exports = { if (defs.length >= 2) { context.report({ node, - message: "Duplicate param '{{name}}'.", + messageId: "unexpected", data: { name: variable.name } }); } diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js b/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js index cc7f8da284..d0fc359736 100644 --- a/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js +++ b/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js @@ -18,7 +18,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-dupe-class-members" }, - schema: [] + schema: [], + + messages: { + unexpected: "Duplicate name '{{name}}'." + } }, create(context) { @@ -102,7 +106,7 @@ module.exports = { } if (isDuplicate) { - context.report({ node, message: "Duplicate name '{{name}}'.", data: { name } }); + context.report({ node, messageId: "unexpected", data: { name } }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-keys.js b/tools/node_modules/eslint/lib/rules/no-dupe-keys.js index 10955e4503..577710c65d 100644 --- a/tools/node_modules/eslint/lib/rules/no-dupe-keys.js +++ b/tools/node_modules/eslint/lib/rules/no-dupe-keys.js @@ -91,7 +91,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-dupe-keys" }, - schema: [] + schema: [], + + messages: { + unexpected: "Duplicate key '{{name}}'." + } }, create(context) { @@ -123,7 +127,7 @@ module.exports = { context.report({ node: info.node, loc: node.key.loc, - message: "Duplicate key '{{name}}'.", + messageId: "unexpected", data: { name } }); } diff --git a/tools/node_modules/eslint/lib/rules/no-duplicate-case.js b/tools/node_modules/eslint/lib/rules/no-duplicate-case.js index 2a9d955111..128b1fc1b1 100644 --- a/tools/node_modules/eslint/lib/rules/no-duplicate-case.js +++ b/tools/node_modules/eslint/lib/rules/no-duplicate-case.js @@ -19,7 +19,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-duplicate-case" }, - schema: [] + schema: [], + + messages: { + unexpected: "Duplicate case label." + } }, create(context) { @@ -33,7 +37,7 @@ module.exports = { const key = sourceCode.getText(switchCase.test); if (mapping[key]) { - context.report({ node: switchCase, message: "Duplicate case label." }); + context.report({ node: switchCase, messageId: "unexpected" }); } else { mapping[key] = switchCase; } diff --git a/tools/node_modules/eslint/lib/rules/no-else-return.js b/tools/node_modules/eslint/lib/rules/no-else-return.js index 91cb5b9796..5e58acfe05 100644 --- a/tools/node_modules/eslint/lib/rules/no-else-return.js +++ b/tools/node_modules/eslint/lib/rules/no-else-return.js @@ -34,7 +34,12 @@ module.exports = { }, additionalProperties: false }], - fixable: "code" + + fixable: "code", + + messages: { + unexpected: "Unnecessary 'else' after 'return'." + } }, create(context) { @@ -52,7 +57,7 @@ module.exports = { function displayReport(node) { context.report({ node, - message: "Unnecessary 'else' after 'return'.", + messageId: "unexpected", fix: fixer => { const sourceCode = context.getSourceCode(); const startToken = sourceCode.getFirstToken(node); @@ -212,8 +217,6 @@ module.exports = { */ function checkIfWithoutElse(node) { const parent = node.parent; - let consequents, - alternate; /* * Fixing this would require splitting one statement into two, so no error should @@ -223,12 +226,15 @@ module.exports = { return; } - for (consequents = []; node.type === "IfStatement"; node = node.alternate) { - if (!node.alternate) { + const consequents = []; + let alternate; + + for (let currentNode = node; currentNode.type === "IfStatement"; currentNode = currentNode.alternate) { + if (!currentNode.alternate) { return; } - consequents.push(node.consequent); - alternate = node.alternate; + consequents.push(currentNode.consequent); + alternate = currentNode.alternate; } if (consequents.every(alwaysReturns)) { diff --git a/tools/node_modules/eslint/lib/rules/no-empty-character-class.js b/tools/node_modules/eslint/lib/rules/no-empty-character-class.js index 3c4806632e..e3f06b069a 100644 --- a/tools/node_modules/eslint/lib/rules/no-empty-character-class.js +++ b/tools/node_modules/eslint/lib/rules/no-empty-character-class.js @@ -21,7 +21,7 @@ * 4. `[gimuy]*`: optional regexp flags * 5. `$`: fix the match at the end of the string */ -const regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+])*\/[gimuy]*$/; +const regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+])*\/[gimuys]*$/; //------------------------------------------------------------------------------ // Rule Definition @@ -36,7 +36,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-empty-character-class" }, - schema: [] + schema: [], + + messages: { + unexpected: "Empty class." + } }, create(context) { @@ -48,7 +52,7 @@ module.exports = { const token = sourceCode.getFirstToken(node); if (token.type === "RegularExpression" && !regex.test(token.value)) { - context.report({ node, message: "Empty class." }); + context.report({ node, messageId: "unexpected" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-empty-function.js b/tools/node_modules/eslint/lib/rules/no-empty-function.js index 3852774e6b..d9948cd4a3 100644 --- a/tools/node_modules/eslint/lib/rules/no-empty-function.js +++ b/tools/node_modules/eslint/lib/rules/no-empty-function.js @@ -109,7 +109,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpected: "Unexpected empty {{name}}." + } }, create(context) { @@ -146,7 +150,7 @@ module.exports = { context.report({ node, loc: node.body.loc.start, - message: "Unexpected empty {{name}}.", + messageId: "unexpected", data: { name } }); } diff --git a/tools/node_modules/eslint/lib/rules/no-empty-pattern.js b/tools/node_modules/eslint/lib/rules/no-empty-pattern.js index 1d0c3ab4b1..939710560f 100644 --- a/tools/node_modules/eslint/lib/rules/no-empty-pattern.js +++ b/tools/node_modules/eslint/lib/rules/no-empty-pattern.js @@ -17,19 +17,23 @@ module.exports = { url: "https://eslint.org/docs/rules/no-empty-pattern" }, - schema: [] + schema: [], + + messages: { + unexpected: "Unexpected empty {{type}} pattern." + } }, create(context) { return { ObjectPattern(node) { if (node.properties.length === 0) { - context.report({ node, message: "Unexpected empty object pattern." }); + context.report({ node, messageId: "unexpected", data: { type: "object" } }); } }, ArrayPattern(node) { if (node.elements.length === 0) { - context.report({ node, message: "Unexpected empty array pattern." }); + context.report({ node, messageId: "unexpected", data: { type: "array" } }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-empty.js b/tools/node_modules/eslint/lib/rules/no-empty.js index 15f1df6790..a598d40f8c 100644 --- a/tools/node_modules/eslint/lib/rules/no-empty.js +++ b/tools/node_modules/eslint/lib/rules/no-empty.js @@ -33,7 +33,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpected: "Empty {{type}} statement." + } }, create(context) { @@ -64,13 +68,13 @@ module.exports = { return; } - context.report({ node, message: "Empty block statement." }); + context.report({ node, messageId: "unexpected", data: { type: "block" } }); }, SwitchStatement(node) { if (typeof node.cases === "undefined" || node.cases.length === 0) { - context.report({ node, message: "Empty switch statement." }); + context.report({ node, messageId: "unexpected", data: { type: "switch" } }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-eq-null.js b/tools/node_modules/eslint/lib/rules/no-eq-null.js index befb9d46f0..eadd16de37 100644 --- a/tools/node_modules/eslint/lib/rules/no-eq-null.js +++ b/tools/node_modules/eslint/lib/rules/no-eq-null.js @@ -19,7 +19,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-eq-null" }, - schema: [] + schema: [], + + messages: { + unexpected: "Use '===' to compare with null." + } }, create(context) { @@ -31,7 +35,7 @@ module.exports = { if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || node.left.type === "Literal" && node.left.raw === "null" && badOperator) { - context.report({ node, message: "Use ā===ā to compare with ānullā." }); + context.report({ node, messageId: "unexpected" }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-eval.js b/tools/node_modules/eslint/lib/rules/no-eval.js index 8cf4aa307b..68ed086a3d 100644 --- a/tools/node_modules/eslint/lib/rules/no-eval.js +++ b/tools/node_modules/eslint/lib/rules/no-eval.js @@ -91,7 +91,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpected: "eval can be harmful." + } }, create(context) { @@ -147,20 +151,19 @@ module.exports = { * @returns {void} */ function report(node) { - let locationNode = node; const parent = node.parent; + const locationNode = node.type === "MemberExpression" + ? node.property + : node; - if (node.type === "MemberExpression") { - locationNode = node.property; - } - if (parent.type === "CallExpression" && parent.callee === node) { - node = parent; - } + const reportNode = parent.type === "CallExpression" && parent.callee === node + ? parent + : node; context.report({ - node, + node: reportNode, loc: locationNode.loc.start, - message: "eval can be harmful." + messageId: "unexpected" }); } diff --git a/tools/node_modules/eslint/lib/rules/no-ex-assign.js b/tools/node_modules/eslint/lib/rules/no-ex-assign.js index 6ede2fc210..feace415e5 100644 --- a/tools/node_modules/eslint/lib/rules/no-ex-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-ex-assign.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-ex-assign" }, - schema: [] + schema: [], + + messages: { + unexpected: "Do not assign to the exception parameter." + } }, create(context) { @@ -32,7 +36,7 @@ module.exports = { */ function checkVariable(variable) { astUtils.getModifyingReferences(variable.references).forEach(reference => { - context.report({ node: reference.identifier, message: "Do not assign to the exception parameter." }); + context.report({ node: reference.identifier, messageId: "unexpected" }); }); } diff --git a/tools/node_modules/eslint/lib/rules/no-extend-native.js b/tools/node_modules/eslint/lib/rules/no-extend-native.js index 2e170017a8..3ba13090a6 100644 --- a/tools/node_modules/eslint/lib/rules/no-extend-native.js +++ b/tools/node_modules/eslint/lib/rules/no-extend-native.js @@ -45,7 +45,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpected: "{{builtin}} prototype is read only, properties should not be added." + } }, create(context) { @@ -67,7 +71,7 @@ module.exports = { function reportNode(node, builtin) { context.report({ node, - message: "{{builtin}} prototype is read only, properties should not be added.", + messageId: "unexpected", data: { builtin } diff --git a/tools/node_modules/eslint/lib/rules/no-extra-bind.js b/tools/node_modules/eslint/lib/rules/no-extra-bind.js index 21b96c3c26..8d901808fd 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-bind.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-bind.js @@ -25,7 +25,11 @@ module.exports = { schema: [], - fixable: "code" + fixable: "code", + + messages: { + unexpected: "The function binding is unnecessary." + } }, create(context) { @@ -41,7 +45,7 @@ module.exports = { function report(node) { context.report({ node: node.parent.parent, - message: "The function binding is unnecessary.", + messageId: "unexpected", loc: node.parent.property.loc.start, fix(fixer) { const firstTokenToRemove = context.getSourceCode() diff --git a/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js b/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js index 471e8b5b82..3819136171 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js @@ -26,7 +26,12 @@ module.exports = { schema: [], - fixable: "code" + fixable: "code", + + messages: { + unexpectedCall: "Redundant Boolean call.", + unexpectedNegation: "Redundant double negation." + } }, create(context) { @@ -82,7 +87,7 @@ module.exports = { ) { context.report({ node, - message: "Redundant double negation.", + messageId: "unexpectedNegation", fix: fixer => fixer.replaceText(parent, sourceCode.getText(node.argument)) }); } @@ -97,7 +102,7 @@ module.exports = { if (isInBooleanContext(node, parent)) { context.report({ node, - message: "Redundant Boolean call.", + messageId: "unexpectedCall", fix: fixer => { if (!node.arguments.length) { return fixer.replaceText(parent, "true"); diff --git a/tools/node_modules/eslint/lib/rules/no-extra-label.js b/tools/node_modules/eslint/lib/rules/no-extra-label.js index f90a403cbf..73a3fea9c9 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-label.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-label.js @@ -26,7 +26,11 @@ module.exports = { schema: [], - fixable: "code" + fixable: "code", + + messages: { + unexpected: "This label '{{name}}' is unnecessary." + } }, create(context) { @@ -109,7 +113,7 @@ module.exports = { if (info.breakable && info.label && info.label.name === labelNode.name) { context.report({ node: labelNode, - message: "This label '{{name}}' is unnecessary.", + messageId: "unexpected", data: labelNode, fix: fixer => fixer.removeRange([sourceCode.getFirstToken(node).range[1], labelNode.range[1]]) }); diff --git a/tools/node_modules/eslint/lib/rules/no-extra-parens.js b/tools/node_modules/eslint/lib/rules/no-extra-parens.js index 4bf8f99500..9765dfa777 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-parens.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-parens.js @@ -55,6 +55,10 @@ module.exports = { maxItems: 2 } ] + }, + + messages: { + unexpected: "Gratuitous parentheses around expression." } }, @@ -163,12 +167,13 @@ module.exports = { * @private */ function isInReturnStatement(node) { - while (node) { - if (node.type === "ReturnStatement" || - (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement")) { + for (let currentNode = node; currentNode; currentNode = currentNode.parent) { + if ( + currentNode.type === "ReturnStatement" || + (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement") + ) { return true; } - node = node.parent; } return false; @@ -312,7 +317,7 @@ module.exports = { context.report({ node, loc: leftParenToken.loc.start, - message: "Gratuitous parentheses around expression.", + messageId: "unexpected", fix(fixer) { const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); diff --git a/tools/node_modules/eslint/lib/rules/no-extra-semi.js b/tools/node_modules/eslint/lib/rules/no-extra-semi.js index 5668c7e095..a074108858 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-semi.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-semi.js @@ -26,7 +26,11 @@ module.exports = { }, fixable: "code", - schema: [] + schema: [], + + messages: { + unexpected: "Unnecessary semicolon." + } }, create(context) { @@ -40,7 +44,7 @@ module.exports = { function report(nodeOrToken) { context.report({ node: nodeOrToken, - message: "Unnecessary semicolon.", + messageId: "unexpected", fix(fixer) { /* diff --git a/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js b/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js index 7efab83935..1dd4d431d7 100644 --- a/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js +++ b/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js @@ -20,7 +20,6 @@ const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"]; * @returns {Object} The parsed and normalized option object. */ function parseOptions(options) { - options = options || {}; return { boolean: "boolean" in options ? Boolean(options.boolean) : true, number: "number" in options ? Boolean(options.number) : true, @@ -186,7 +185,7 @@ module.exports = { }, create(context) { - const options = parseOptions(context.options[0]); + const options = parseOptions(context.options[0] || {}); const sourceCode = context.getSourceCode(); /** @@ -197,8 +196,6 @@ module.exports = { * @returns {void} */ function report(node, recommendation, shouldFix) { - shouldFix = typeof shouldFix === "undefined" ? true : shouldFix; - context.report({ node, message: "use `{{recommendation}}` instead.", @@ -233,7 +230,7 @@ module.exports = { if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`; - report(node, recommendation); + report(node, recommendation, true); } // ~foo.indexOf(bar) @@ -249,7 +246,7 @@ module.exports = { if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { const recommendation = `Number(${sourceCode.getText(node.argument)})`; - report(node, recommendation); + report(node, recommendation, true); } }, @@ -264,7 +261,7 @@ module.exports = { if (nonNumericOperand) { const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; - report(node, recommendation); + report(node, recommendation, true); } // "" + foo @@ -272,7 +269,7 @@ module.exports = { if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`; - report(node, recommendation); + report(node, recommendation, true); } }, @@ -285,7 +282,7 @@ module.exports = { const code = sourceCode.getText(getNonEmptyOperand(node)); const recommendation = `${code} = String(${code})`; - report(node, recommendation); + report(node, recommendation, true); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js b/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js index 8ccb5242b0..7169e0ca77 100644 --- a/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js +++ b/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js @@ -8,7 +8,10 @@ // Requirements //------------------------------------------------------------------------------ -const espree = require("espree"); +const RegExpValidator = require("regexpp").RegExpValidator; +const validator = new RegExpValidator({ ecmaVersion: 2018 }); +const validFlags = /[gimuys]/g; +const undefined1 = void 0; //------------------------------------------------------------------------------ // Rule Definition @@ -40,10 +43,14 @@ module.exports = { create(context) { const options = context.options[0]; - let allowedFlags = ""; + let allowedFlags = null; if (options && options.allowConstructorFlags) { - allowedFlags = options.allowConstructorFlags.join(""); + const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); + + if (temp) { + allowedFlags = new RegExp(`[${temp}]`, "gi"); + } } /** @@ -57,51 +64,61 @@ module.exports = { } /** - * Validate strings passed to the RegExp constructor - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private + * Check syntax error in a given pattern. + * @param {string} pattern The RegExp pattern to validate. + * @param {boolean} uFlag The Unicode flag. + * @returns {string|null} The syntax error. + */ + function validateRegExpPattern(pattern, uFlag) { + try { + validator.validatePattern(pattern, undefined1, undefined1, uFlag); + return null; + } catch (err) { + return err.message; + } + } + + /** + * Check syntax error in a given flags. + * @param {string} flags The RegExp flags to validate. + * @returns {string|null} The syntax error. */ - function check(node) { - if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) { + function validateRegExpFlags(flags) { + try { + validator.validateFlags(flags); + return null; + } catch (err) { + return `Invalid flags supplied to RegExp constructor '${flags}'`; + } + } + + return { + "CallExpression, NewExpression"(node) { + if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) { + return; + } + const pattern = node.arguments[0].value; let flags = isString(node.arguments[1]) ? node.arguments[1].value : ""; if (allowedFlags) { - flags = flags.replace(new RegExp(`[${allowedFlags}]`, "gi"), ""); + flags = flags.replace(allowedFlags, ""); } - try { - void new RegExp(node.arguments[0].value); - } catch (e) { + // If flags are unknown, check both are errored or not. + const message = validateRegExpFlags(flags) || ( + flags + ? validateRegExpPattern(pattern, flags.indexOf("u") !== -1) + : validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) + ); + + if (message) { context.report({ node, message: "{{message}}.", - data: e + data: { message } }); } - - if (flags) { - - try { - espree.parse(`/./${flags}`, context.parserOptions); - } catch (ex) { - context.report({ - node, - message: "Invalid flags supplied to RegExp constructor '{{flags}}'.", - data: { - flags - } - }); - } - } - } - } - - return { - CallExpression: check, - NewExpression: check }; - } }; diff --git a/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js b/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js index e36ec88b01..f1840aaf2d 100644 --- a/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js +++ b/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js @@ -101,7 +101,7 @@ module.exports = { */ function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { const shouldCheckStrings = skipStrings && (typeof node.value === "string"); - const shouldCheckRegExps = skipRegExps && (node.value instanceof RegExp); + const shouldCheckRegExps = skipRegExps && Boolean(node.regex); if (shouldCheckStrings || shouldCheckRegExps) { diff --git a/tools/node_modules/eslint/lib/rules/no-loop-func.js b/tools/node_modules/eslint/lib/rules/no-loop-func.js index 0dce09a61a..d103cb5335 100644 --- a/tools/node_modules/eslint/lib/rules/no-loop-func.js +++ b/tools/node_modules/eslint/lib/rules/no-loop-func.js @@ -20,9 +20,9 @@ * `null`. */ function getContainingLoopNode(node) { - let parent = node.parent; + for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) { + const parent = currentNode.parent; - while (parent) { switch (parent.type) { case "WhileStatement": case "DoWhileStatement": @@ -31,7 +31,7 @@ function getContainingLoopNode(node) { case "ForStatement": // `init` is outside of the loop. - if (parent.init !== node) { + if (parent.init !== currentNode) { return parent; } break; @@ -40,7 +40,7 @@ function getContainingLoopNode(node) { case "ForOfStatement": // `right` is outside of the loop. - if (parent.right !== node) { + if (parent.right !== currentNode) { return parent; } break; @@ -55,9 +55,6 @@ function getContainingLoopNode(node) { default: break; } - - node = parent; - parent = node.parent; } return null; @@ -73,12 +70,13 @@ function getContainingLoopNode(node) { * @returns {ASTNode} The most outer loop node. */ function getTopLoopNode(node, excludedNode) { - let retv = node; const border = excludedNode ? excludedNode.range[1] : 0; + let retv = node; + let containingLoopNode = node; - while (node && node.range[0] >= border) { - retv = node; - node = getContainingLoopNode(node); + while (containingLoopNode && containingLoopNode.range[0] >= border) { + retv = containingLoopNode; + containingLoopNode = getContainingLoopNode(containingLoopNode); } return retv; diff --git a/tools/node_modules/eslint/lib/rules/no-magic-numbers.js b/tools/node_modules/eslint/lib/rules/no-magic-numbers.js index 20a752e554..2826dbf493 100644 --- a/tools/node_modules/eslint/lib/rules/no-magic-numbers.js +++ b/tools/node_modules/eslint/lib/rules/no-magic-numbers.js @@ -101,25 +101,32 @@ module.exports = { return { Literal(node) { - let parent = node.parent, - value = node.value, - raw = node.raw; const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; if (!isNumber(node)) { return; } + let fullNumberNode; + let parent; + let value; + let raw; + // For negative magic numbers: update the value and parent node - if (parent.type === "UnaryExpression" && parent.operator === "-") { - node = parent; + if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") { + fullNumberNode = node.parent; + parent = fullNumberNode.parent; + value = -node.value; + raw = `-${node.raw}`; + } else { + fullNumberNode = node; parent = node.parent; - value = -value; - raw = `-${raw}`; + value = node.value; + raw = node.raw; } if (shouldIgnoreNumber(value) || - shouldIgnoreParseInt(parent, node) || + shouldIgnoreParseInt(parent, fullNumberNode) || shouldIgnoreArrayIndexes(parent) || shouldIgnoreJSXNumbers(parent)) { return; @@ -128,7 +135,7 @@ module.exports = { if (parent.type === "VariableDeclarator") { if (enforceConst && parent.parent.kind !== "const") { context.report({ - node, + node: fullNumberNode, message: "Number constants declarations must use 'const'." }); } @@ -137,7 +144,7 @@ module.exports = { (parent.type === "AssignmentExpression" && parent.left.type === "Identifier") ) { context.report({ - node, + node: fullNumberNode, message: "No magic number: {{raw}}.", data: { raw diff --git a/tools/node_modules/eslint/lib/rules/no-return-assign.js b/tools/node_modules/eslint/lib/rules/no-return-assign.js index 0a016cfad5..ca96da9f2f 100644 --- a/tools/node_modules/eslint/lib/rules/no-return-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-return-assign.js @@ -46,11 +46,12 @@ module.exports = { return; } - let parent = node.parent; + let currentChild = node; + let parent = currentChild.parent; // Find ReturnStatement or ArrowFunctionExpression in ancestors. while (parent && !SENTINEL_TYPE.test(parent.type)) { - node = parent; + currentChild = parent; parent = parent.parent; } @@ -60,7 +61,7 @@ module.exports = { node: parent, message: "Return statement should not contain assignment." }); - } else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === node) { + } else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === currentChild) { context.report({ node: parent, message: "Arrow function should not return assignment." diff --git a/tools/node_modules/eslint/lib/rules/no-self-assign.js b/tools/node_modules/eslint/lib/rules/no-self-assign.js index 3014042002..8091d7d2e5 100644 --- a/tools/node_modules/eslint/lib/rules/no-self-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-self-assign.js @@ -121,7 +121,9 @@ function eachSelfAssignment(left, right, props, report) { let startJ = 0; for (let i = right.properties.length - 1; i >= 0; --i) { - if (right.properties[i].type === "ExperimentalSpreadProperty") { + const propType = right.properties[i].type; + + if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") { startJ = i + 1; break; } diff --git a/tools/node_modules/eslint/lib/rules/no-unexpected-multiline.js b/tools/node_modules/eslint/lib/rules/no-unexpected-multiline.js index c7c26686d9..51ba7cfa75 100644 --- a/tools/node_modules/eslint/lib/rules/no-unexpected-multiline.js +++ b/tools/node_modules/eslint/lib/rules/no-unexpected-multiline.js @@ -33,7 +33,7 @@ module.exports = { const TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal."; const DIVISION_MESSAGE = "Unexpected newline between numerator and division operator."; - const REGEX_FLAG_MATCHER = /^[gimuy]+$/; + const REGEX_FLAG_MATCHER = /^[gimsuy]+$/; const sourceCode = context.getSourceCode(); diff --git a/tools/node_modules/eslint/lib/rules/no-unsafe-finally.js b/tools/node_modules/eslint/lib/rules/no-unsafe-finally.js index ebef05188f..1ebdd2e377 100644 --- a/tools/node_modules/eslint/lib/rules/no-unsafe-finally.js +++ b/tools/node_modules/eslint/lib/rules/no-unsafe-finally.js @@ -60,17 +60,20 @@ module.exports = { sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW; } - while (node && !sentinelNodeType.test(node.type)) { - if (node.parent.label && label && (node.parent.label.name === label.name)) { + for ( + let currentNode = node; + currentNode && !sentinelNodeType.test(currentNode.type); + currentNode = currentNode.parent + ) { + if (currentNode.parent.label && label && (currentNode.parent.label.name === label.name)) { labelInside = true; } - if (isFinallyBlock(node)) { + if (isFinallyBlock(currentNode)) { if (label && labelInside) { return false; } return true; } - node = node.parent; } return false; } diff --git a/tools/node_modules/eslint/lib/rules/no-unused-vars.js b/tools/node_modules/eslint/lib/rules/no-unused-vars.js index 1d0cef8562..6ba5734986 100644 --- a/tools/node_modules/eslint/lib/rules/no-unused-vars.js +++ b/tools/node_modules/eslint/lib/rules/no-unused-vars.js @@ -65,7 +65,7 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); - const REST_PROPERTY_TYPE = /^(?:Experimental)?RestProperty$/; + const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/; const config = { vars: "all", 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 80abec78e8..efc9706f41 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-escape.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-escape.js @@ -25,8 +25,8 @@ function union(setA, setB) { } const VALID_STRING_ESCAPES = union(new Set("\\nrvtbfux"), astUtils.LINEBREAKS); -const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnrsStvwWxu0123456789]"); -const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()B")); +const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnpPrsStvwWxu0123456789]"); +const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk")); /** * Parses a regular expression into a list of characters with character class info. diff --git a/tools/node_modules/eslint/lib/rules/no-useless-return.js b/tools/node_modules/eslint/lib/rules/no-useless-return.js index 8e2a6d97f6..d801c0e465 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-return.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-return.js @@ -56,12 +56,14 @@ function isRemovable(node) { * @returns {boolean} `true` if the node is in a `finally` block. */ function isInFinally(node) { - while (node && node.parent && !astUtils.isFunction(node)) { - if (node.parent.type === "TryStatement" && node.parent.finalizer === node) { + for ( + let currentNode = node; + currentNode && currentNode.parent && !astUtils.isFunction(currentNode); + currentNode = currentNode.parent + ) { + if (currentNode.parent.type === "TryStatement" && currentNode.parent.finalizer === currentNode) { return true; } - - node = node.parent; } return false; @@ -116,13 +118,12 @@ module.exports = { * * @param {ASTNode[]} uselessReturns - The collected return statements. * @param {CodePathSegment[]} prevSegments - The previous segments to traverse. - * @param {WeakSet<CodePathSegment>} [traversedSegments] A set of segments that have already been traversed in this call + * @param {WeakSet<CodePathSegment>} [providedTraversedSegments] A set of segments that have already been traversed in this call * @returns {ASTNode[]} `uselessReturns`. */ - function getUselessReturns(uselessReturns, prevSegments, traversedSegments) { - if (!traversedSegments) { - traversedSegments = new WeakSet(); - } + function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) { + const traversedSegments = providedTraversedSegments || new WeakSet(); + for (const segment of prevSegments) { if (!segment.reachable) { if (!traversedSegments.has(segment)) { diff --git a/tools/node_modules/eslint/lib/rules/no-var.js b/tools/node_modules/eslint/lib/rules/no-var.js index d95ca539f0..5ca868e65a 100644 --- a/tools/node_modules/eslint/lib/rules/no-var.js +++ b/tools/node_modules/eslint/lib/rules/no-var.js @@ -33,10 +33,12 @@ function isGlobal(variable) { * scope. */ function getEnclosingFunctionScope(scope) { - while (scope.type !== "function" && scope.type !== "global") { - scope = scope.upper; + let currentScope = scope; + + while (currentScope.type !== "function" && currentScope.type !== "global") { + currentScope = currentScope.upper; } - return scope; + return currentScope; } /** @@ -87,12 +89,10 @@ const SCOPE_NODE_TYPE = /^(?:Program|BlockStatement|SwitchStatement|ForStatement * `ForOfStatement`. */ function getScopeNode(node) { - while (node) { - if (SCOPE_NODE_TYPE.test(node.type)) { - return node; + for (let currentNode = node; currentNode; currentNode = currentNode.parent) { + if (SCOPE_NODE_TYPE.test(currentNode.type)) { + return currentNode; } - - node = node.parent; } /* istanbul ignore next : unreachable */ diff --git a/tools/node_modules/eslint/lib/rules/object-curly-newline.js b/tools/node_modules/eslint/lib/rules/object-curly-newline.js index 91b2ca6c97..39043a8b9b 100644 --- a/tools/node_modules/eslint/lib/rules/object-curly-newline.js +++ b/tools/node_modules/eslint/lib/rules/object-curly-newline.js @@ -10,6 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("../ast-utils"); +const lodash = require("lodash"); //------------------------------------------------------------------------------ // Helpers @@ -73,19 +74,58 @@ function normalizeOptionValue(value) { * Normalizes a given option value. * * @param {string|Object|undefined} options - An option value to parse. - * @returns {{ObjectExpression: {multiline: boolean, minProperties: number}, ObjectPattern: {multiline: boolean, minProperties: number}}} Normalized option object. + * @returns {{ + * ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean}, + * ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean}, + * ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean}, + * ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean} + * }} Normalized option object. */ function normalizeOptions(options) { - if (options && (options.ObjectExpression || options.ObjectPattern)) { + const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]); + + if (lodash.isPlainObject(options) && lodash.some(options, isNodeSpecificOption)) { return { ObjectExpression: normalizeOptionValue(options.ObjectExpression), - ObjectPattern: normalizeOptionValue(options.ObjectPattern) + ObjectPattern: normalizeOptionValue(options.ObjectPattern), + ImportDeclaration: normalizeOptionValue(options.ImportDeclaration), + ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration) }; } const value = normalizeOptionValue(options); - return { ObjectExpression: value, ObjectPattern: value }; + return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value }; +} + +/** + * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration + * node needs to be checked for missing line breaks + * + * @param {ASTNode} node - Node under inspection + * @param {Object} options - option specific to node type + * @param {Token} first - First object property + * @param {Token} last - Last object property + * @returns {boolean} `true` if node needs to be checked for missing line breaks + */ +function areLineBreaksRequired(node, options, first, last) { + let objectProperties; + + if (node.type === "ObjectExpression" || node.type === "ObjectPattern") { + objectProperties = node.properties; + } else { + + // is ImportDeclaration or ExportNamedDeclaration + objectProperties = node.specifiers + .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier"); + } + + return objectProperties.length >= options.minProperties || + ( + options.multiline && + objectProperties.length > 0 && + first.loc.start.line !== last.loc.end.line + ); } //------------------------------------------------------------------------------ @@ -109,7 +149,9 @@ module.exports = { type: "object", properties: { ObjectExpression: OPTION_VALUE, - ObjectPattern: OPTION_VALUE + ObjectPattern: OPTION_VALUE, + ImportDeclaration: OPTION_VALUE, + ExportDeclaration: OPTION_VALUE }, additionalProperties: false, minProperties: 1 @@ -125,32 +167,37 @@ module.exports = { /** * Reports a given node if it violated this rule. - * - * @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node. - * @param {{multiline: boolean, minProperties: number}} options - An option object. + * @param {ASTNode} node - A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node. + * @param {{multiline: boolean, minProperties: number, consistent: boolean}} options - An option object. * @returns {void} */ function check(node) { const options = normalizedOptions[node.type]; + + if ( + (node.type === "ImportDeclaration" && + !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) || + (node.type === "ExportNamedDeclaration" && + !node.specifiers.some(specifier => specifier.type === "ExportSpecifier")) + ) { + return; + } + const openBrace = sourceCode.getFirstToken(node, token => token.value === "{"); + let closeBrace; if (node.typeAnnotation) { closeBrace = sourceCode.getTokenBefore(node.typeAnnotation); } else { - closeBrace = sourceCode.getLastToken(node); + closeBrace = sourceCode.getLastToken(node, token => token.value === "}"); } let first = sourceCode.getTokenAfter(openBrace, { includeComments: true }); let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true }); - const needsLinebreaks = ( - node.properties.length >= options.minProperties || - ( - options.multiline && - node.properties.length > 0 && - first.loc.start.line !== last.loc.end.line - ) - ); + + const needsLineBreaks = areLineBreaksRequired(node, options, first, last); + const hasCommentsFirstToken = astUtils.isCommentToken(first); const hasCommentsLastToken = astUtils.isCommentToken(last); @@ -165,7 +212,7 @@ module.exports = { first = sourceCode.getTokenAfter(openBrace); last = sourceCode.getTokenBefore(closeBrace); - if (needsLinebreaks) { + if (needsLineBreaks) { if (astUtils.isTokenOnSameLine(openBrace, first)) { context.report({ message: "Expected a line break after this opening brace.", @@ -244,7 +291,9 @@ module.exports = { return { ObjectExpression: check, - ObjectPattern: check + ObjectPattern: check, + ImportDeclaration: check, + ExportNamedDeclaration: check }; } }; diff --git a/tools/node_modules/eslint/lib/rules/object-property-newline.js b/tools/node_modules/eslint/lib/rules/object-property-newline.js index 56ca269402..65baf0a95e 100644 --- a/tools/node_modules/eslint/lib/rules/object-property-newline.js +++ b/tools/node_modules/eslint/lib/rules/object-property-newline.js @@ -22,7 +22,10 @@ module.exports = { { type: "object", properties: { - allowMultiplePropertiesPerLine: { + allowAllPropertiesOnSameLine: { + type: "boolean" + }, + allowMultiplePropertiesPerLine: { // Deprecated type: "boolean" } }, @@ -34,7 +37,10 @@ module.exports = { }, create(context) { - const allowSameLine = context.options[0] && Boolean(context.options[0].allowMultiplePropertiesPerLine); + const allowSameLine = context.options[0] && ( + Boolean(context.options[0].allowAllPropertiesOnSameLine) || + Boolean(context.options[0].allowMultiplePropertiesPerLine) // Deprecated + ); const errorMessage = allowSameLine ? "Object properties must go on a new line if they aren't all on the same line." : "Object properties must go on a new line."; diff --git a/tools/node_modules/eslint/lib/rules/object-shorthand.js b/tools/node_modules/eslint/lib/rules/object-shorthand.js index c6c0b10445..c5239a71d1 100644 --- a/tools/node_modules/eslint/lib/rules/object-shorthand.js +++ b/tools/node_modules/eslint/lib/rules/object-shorthand.js @@ -131,7 +131,7 @@ module.exports = { * */ function canHaveShorthand(property) { - return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty"); + return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty"); } /** @@ -233,10 +233,11 @@ module.exports = { const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); let keyPrefix = ""; + if (node.value.async) { + keyPrefix += "async "; + } if (node.value.generator) { - keyPrefix = "*"; - } else if (node.value.async) { - keyPrefix = "async "; + keyPrefix += "*"; } if (node.value.type === "FunctionExpression") { @@ -273,10 +274,11 @@ module.exports = { const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); let functionHeader = "function"; + if (node.value.async) { + functionHeader = `async ${functionHeader}`; + } if (node.value.generator) { - functionHeader = "function*"; - } else if (node.value.async) { - functionHeader = "async function"; + functionHeader = `${functionHeader}*`; } return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`); diff --git a/tools/node_modules/eslint/lib/rules/one-var.js b/tools/node_modules/eslint/lib/rules/one-var.js index cd094444b6..40b5f0f0aa 100644 --- a/tools/node_modules/eslint/lib/rules/one-var.js +++ b/tools/node_modules/eslint/lib/rules/one-var.js @@ -22,7 +22,7 @@ module.exports = { { oneOf: [ { - enum: ["always", "never"] + enum: ["always", "never", "consecutive"] }, { type: "object", @@ -31,13 +31,13 @@ module.exports = { type: "boolean" }, var: { - enum: ["always", "never"] + enum: ["always", "never", "consecutive"] }, let: { - enum: ["always", "never"] + enum: ["always", "never", "consecutive"] }, const: { - enum: ["always", "never"] + enum: ["always", "never", "consecutive"] } }, additionalProperties: false @@ -46,10 +46,10 @@ module.exports = { type: "object", properties: { initialized: { - enum: ["always", "never"] + enum: ["always", "never", "consecutive"] }, uninitialized: { - enum: ["always", "never"] + enum: ["always", "never", "consecutive"] } }, additionalProperties: false @@ -60,10 +60,9 @@ module.exports = { }, create(context) { - - const MODE_ALWAYS = "always", - MODE_NEVER = "never"; - + const MODE_ALWAYS = "always"; + const MODE_NEVER = "never"; + const MODE_CONSECUTIVE = "consecutive"; const mode = context.options[0] || MODE_ALWAYS; const options = {}; @@ -273,119 +272,163 @@ module.exports = { return true; } + /** + * Checks a given VariableDeclaration node for errors. + * @param {ASTNode} node The VariableDeclaration node to check + * @returns {void} + * @private + */ + function checkVariableDeclaration(node) { + const parent = node.parent; + const type = node.kind; - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + if (!options[type]) { + return; + } - return { - Program: startFunction, - FunctionDeclaration: startFunction, - FunctionExpression: startFunction, - ArrowFunctionExpression: startFunction, - BlockStatement: startBlock, - ForStatement: startBlock, - ForInStatement: startBlock, - ForOfStatement: startBlock, - SwitchStatement: startBlock, + const declarations = node.declarations; + const declarationCounts = countDeclarations(declarations); + const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire); + + if (options[type].initialized === MODE_ALWAYS) { + if (options.separateRequires && mixedRequires) { + context.report({ + node, + message: "Split requires to be separated into a single block." + }); + } + } - VariableDeclaration(node) { - const parent = node.parent; - const type = node.kind; + // consecutive + const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0; - if (!options[type]) { - return; - } + if (nodeIndex > 0) { + const previousNode = parent.body[nodeIndex - 1]; + const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration"; - const declarations = node.declarations; - const declarationCounts = countDeclarations(declarations); - const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire); + if (isPreviousNodeDeclaration && previousNode.kind === type) { + const previousDeclCounts = countDeclarations(previousNode.declarations); - if (options[type].initialized === MODE_ALWAYS) { - if (options.separateRequires && mixedRequires) { + if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) { + context.report({ + node, + message: "Combine this with the previous '{{type}}' statement.", + data: { + type + } + }); + } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) { context.report({ node, - message: "Split requires to be separated into a single block." + message: "Combine this with the previous '{{type}}' statement with initialized variables.", + data: { + type + } + }); + } else if (options[type].uninitialized === MODE_CONSECUTIVE && + declarationCounts.uninitialized > 0 && + previousDeclCounts.uninitialized > 0) { + context.report({ + node, + message: "Combine this with the previous '{{type}}' statement with uninitialized variables.", + data: { + type + } }); } } + } - // always - if (!hasOnlyOneStatement(type, declarations)) { - if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { + // always + if (!hasOnlyOneStatement(type, declarations)) { + if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { + context.report({ + node, + message: "Combine this with the previous '{{type}}' statement.", + data: { + type + } + }); + } else { + if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) { context.report({ node, - message: "Combine this with the previous '{{type}}' statement.", + message: "Combine this with the previous '{{type}}' statement with initialized variables.", data: { type } }); - } else { - if (options[type].initialized === MODE_ALWAYS) { - context.report({ - node, - message: "Combine this with the previous '{{type}}' statement with initialized variables.", - data: { - type - } - }); + } + if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) { + if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) { + return; } - if (options[type].uninitialized === MODE_ALWAYS) { - if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) { - return; + context.report({ + node, + message: "Combine this with the previous '{{type}}' statement with uninitialized variables.", + data: { + type } - context.report({ - node, - message: "Combine this with the previous '{{type}}' statement with uninitialized variables.", - data: { - type - } - }); - } + }); } } + } - // never - if (parent.type !== "ForStatement" || parent.init !== node) { - const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized; - - if (totalDeclarations > 1) { - - if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { - - // both initialized and uninitialized - context.report({ - node, - message: "Split '{{type}}' declarations into multiple statements.", - data: { - type - } - }); - } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) { - - // initialized - context.report({ - node, - message: "Split initialized '{{type}}' declarations into multiple statements.", - data: { - type - } - }); - } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) { - - // uninitialized - context.report({ - node, - message: "Split uninitialized '{{type}}' declarations into multiple statements.", - data: { - type - } - }); - } + // never + if (parent.type !== "ForStatement" || parent.init !== node) { + const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized; + + if (totalDeclarations > 1) { + if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { + + // both initialized and uninitialized + context.report({ + node, + message: "Split '{{type}}' declarations into multiple statements.", + data: { + type + } + }); + } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) { + + // initialized + context.report({ + node, + message: "Split initialized '{{type}}' declarations into multiple statements.", + data: { + type + } + }); + } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) { + + // uninitialized + context.report({ + node, + message: "Split uninitialized '{{type}}' declarations into multiple statements.", + data: { + type + } + }); } } - }, + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + BlockStatement: startBlock, + ForStatement: startBlock, + ForInStatement: startBlock, + ForOfStatement: startBlock, + SwitchStatement: startBlock, + VariableDeclaration: checkVariableDeclaration, "ForStatement:exit": endBlock, "ForOfStatement:exit": endBlock, "ForInStatement:exit": endBlock, diff --git a/tools/node_modules/eslint/lib/rules/padding-line-between-statements.js b/tools/node_modules/eslint/lib/rules/padding-line-between-statements.js index 9d1a4a3c7e..d2254fa6ab 100644 --- a/tools/node_modules/eslint/lib/rules/padding-line-between-statements.js +++ b/tools/node_modules/eslint/lib/rules/padding-line-between-statements.js @@ -358,6 +358,12 @@ const StatementTypes = { node.loc.start.line !== node.loc.end.line && isBlockLikeStatement(sourceCode, node) }, + "multiline-expression": { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + node.type === "ExpressionStatement" && + !isDirectivePrologue(node, sourceCode) + }, block: newNodeTypeTester("BlockStatement"), empty: newNodeTypeTester("EmptyStatement"), @@ -467,13 +473,15 @@ module.exports = { * @private */ function match(node, type) { - while (node.type === "LabeledStatement") { - node = node.body; + let innerStatementNode = node; + + while (innerStatementNode.type === "LabeledStatement") { + innerStatementNode = innerStatementNode.body; } if (Array.isArray(type)) { - return type.some(match.bind(null, node)); + return type.some(match.bind(null, innerStatementNode)); } - return StatementTypes[type].test(node, sourceCode); + return StatementTypes[type].test(innerStatementNode, sourceCode); } /** diff --git a/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js b/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js index ff7a0fa7e7..1bc140b101 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js +++ b/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js @@ -64,9 +64,10 @@ function getVariableOfArguments(scope) { */ function getCallbackInfo(node) { const retv = { isCallback: false, isLexicalThis: false }; + let currentNode = node; let parent = node.parent; - while (node) { + while (currentNode) { switch (parent.type) { // Checks parents recursively. @@ -77,7 +78,7 @@ function getCallbackInfo(node) { // Checks whether the parent node is `.bind(this)` call. case "MemberExpression": - if (parent.object === node && + if (parent.object === currentNode && !parent.property.computed && parent.property.type === "Identifier" && parent.property.name === "bind" && @@ -97,7 +98,7 @@ function getCallbackInfo(node) { // Checks whether the node is a callback. case "CallExpression": case "NewExpression": - if (parent.callee !== node) { + if (parent.callee !== currentNode) { retv.isCallback = true; } return retv; @@ -106,7 +107,7 @@ function getCallbackInfo(node) { return retv; } - node = parent; + currentNode = parent; parent = parent.parent; } diff --git a/tools/node_modules/eslint/lib/rules/prefer-destructuring.js b/tools/node_modules/eslint/lib/rules/prefer-destructuring.js index e9d02da3a1..112ea64613 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-destructuring.js +++ b/tools/node_modules/eslint/lib/rules/prefer-destructuring.js @@ -165,8 +165,10 @@ module.exports = { if (shouldCheck(reportNode.type, "object")) { const property = rightNode.property; - if ((property.type === "Literal" && leftNode.name === property.value) || (property.type === "Identifier" && - leftNode.name === property.name)) { + if ( + (property.type === "Literal" && leftNode.name === property.value) || + (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed) + ) { report(reportNode, "object"); } } diff --git a/tools/node_modules/eslint/lib/rules/prefer-template.js b/tools/node_modules/eslint/lib/rules/prefer-template.js index c583bdcf9a..2b893fd371 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-template.js +++ b/tools/node_modules/eslint/lib/rules/prefer-template.js @@ -30,10 +30,12 @@ function isConcatenation(node) { * @returns {ASTNode} the top binary expression node in parents of a given node. */ function getTopConcatBinaryExpression(node) { - while (isConcatenation(node.parent)) { - node = node.parent; + let currentNode = node; + + while (isConcatenation(currentNode.parent)) { + currentNode = currentNode.parent; } - return node; + return currentNode; } /** diff --git a/tools/node_modules/eslint/lib/rules/require-await.js b/tools/node_modules/eslint/lib/rules/require-await.js index 6adc84ae15..5517cf8672 100644 --- a/tools/node_modules/eslint/lib/rules/require-await.js +++ b/tools/node_modules/eslint/lib/rules/require-await.js @@ -90,6 +90,11 @@ module.exports = { AwaitExpression() { scopeInfo.hasAwait = true; + }, + ForOfStatement(node) { + if (node.await) { + scopeInfo.hasAwait = true; + } } }; } diff --git a/tools/node_modules/eslint/lib/rules/rest-spread-spacing.js b/tools/node_modules/eslint/lib/rules/rest-spread-spacing.js index 1fbc2c4cc5..e87d881298 100644 --- a/tools/node_modules/eslint/lib/rules/rest-spread-spacing.js +++ b/tools/node_modules/eslint/lib/rules/rest-spread-spacing.js @@ -47,9 +47,15 @@ module.exports = { switch (node.type) { case "SpreadElement": type = "spread"; + if (node.parent.type === "ObjectExpression") { + type += " property"; + } break; case "RestElement": type = "rest"; + if (node.parent.type === "ObjectPattern") { + type += " property"; + } break; case "ExperimentalSpreadProperty": type = "spread property"; diff --git a/tools/node_modules/eslint/lib/rules/space-unary-ops.js b/tools/node_modules/eslint/lib/rules/space-unary-ops.js index 4d122836ad..6fbcc15c04 100644 --- a/tools/node_modules/eslint/lib/rules/space-unary-ops.js +++ b/tools/node_modules/eslint/lib/rules/space-unary-ops.js @@ -67,15 +67,6 @@ module.exports = { } /** - * Check if the node's child argument is an "ObjectExpression" - * @param {ASTnode} node AST node - * @returns {boolean} Whether or not the argument's type is "ObjectExpression" - */ - function isArgumentObjectExpression(node) { - return node.argument && node.argument.type && node.argument.type === "ObjectExpression"; - } - - /** * Checks if an override exists for a given operator. * @param {string} operator Operator * @returns {boolean} Whether or not an override has been provided for the operator @@ -125,7 +116,7 @@ module.exports = { * @returns {void} */ function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { - if (isArgumentObjectExpression(node)) { + if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { if (secondToken.range[0] > firstToken.range[1]) { context.report({ node, @@ -150,8 +141,6 @@ module.exports = { * @returns {void} */ function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { - word = word || firstToken.value; - if (overrideExistsForOperator(word)) { if (overrideEnforcesSpaces(word)) { verifyWordHasSpaces(node, firstToken, secondToken, word); @@ -285,7 +274,7 @@ module.exports = { const secondToken = tokens[1]; if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") { - checkUnaryWordOperatorForSpaces(node, firstToken, secondToken); + checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value); return; } diff --git a/tools/node_modules/eslint/lib/rules/spaced-comment.js b/tools/node_modules/eslint/lib/rules/spaced-comment.js index 9d2f5f49ac..3a76c0c260 100644 --- a/tools/node_modules/eslint/lib/rules/spaced-comment.js +++ b/tools/node_modules/eslint/lib/rules/spaced-comment.js @@ -17,10 +17,7 @@ const astUtils = require("../ast-utils"); * @returns {string} An escaped string. */ function escape(s) { - const isOneChar = s.length === 1; - - s = lodash.escapeRegExp(s); - return isOneChar ? s : `(?:${s})`; + return `(?:${lodash.escapeRegExp(s)})`; } /** @@ -40,11 +37,10 @@ function escapeAndRepeat(s) { * @returns {string[]} A marker list. */ function parseMarkersOption(markers) { - markers = markers ? markers.slice(0) : []; // `*` is a marker for JSDoc comments. if (markers.indexOf("*") === -1) { - markers.push("*"); + return markers.concat("*"); } return markers; @@ -244,7 +240,7 @@ module.exports = { const balanced = config.block && config.block.balanced; const styleRules = ["block", "line"].reduce((rule, type) => { - const markers = parseMarkersOption(config[type] && config[type].markers || config.markers); + const markers = parseMarkersOption(config[type] && config[type].markers || config.markers || []); const exceptions = config[type] && config[type].exceptions || config.exceptions || []; const endNeverPattern = "[ \t]+$"; diff --git a/tools/node_modules/eslint/lib/rules/template-tag-spacing.js b/tools/node_modules/eslint/lib/rules/template-tag-spacing.js index aee7ac108b..aee7ac108b 100755..100644 --- a/tools/node_modules/eslint/lib/rules/template-tag-spacing.js +++ b/tools/node_modules/eslint/lib/rules/template-tag-spacing.js diff --git a/tools/node_modules/eslint/lib/rules/valid-jsdoc.js b/tools/node_modules/eslint/lib/rules/valid-jsdoc.js index 5e1af10de7..4038f70e56 100644 --- a/tools/node_modules/eslint/lib/rules/valid-jsdoc.js +++ b/tools/node_modules/eslint/lib/rules/valid-jsdoc.js @@ -57,7 +57,9 @@ module.exports = { }, additionalProperties: false } - ] + ], + + fixable: "code" }, create(context) { @@ -145,23 +147,35 @@ module.exports = { /** * Extract the current and expected type based on the input type object * @param {Object} type JSDoc tag - * @returns {Object} current and expected type object + * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and + * the expected name of the annotation * @private */ function getCurrentExpectedTypes(type) { let currentType; if (type.name) { - currentType = type.name; + currentType = type; } else if (type.expression) { - currentType = type.expression.name; + currentType = type.expression; } - const expectedType = currentType && preferType[currentType]; - return { currentType, - expectedType + expectedTypeName: currentType && preferType[currentType.name] + }; + } + + /** + * Gets the location of a JSDoc node in a file + * @param {Token} jsdocComment The comment that this node is parsed from + * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment + * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag + */ + function getAbsoluteRange(jsdocComment, parsedJsdocNode) { + return { + start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]), + end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1]) }; } @@ -204,14 +218,21 @@ module.exports = { elements.forEach(validateType.bind(null, jsdocNode)); typesToCheck.forEach(typeToCheck => { - if (typeToCheck.expectedType && - typeToCheck.expectedType !== typeToCheck.currentType) { + if (typeToCheck.expectedTypeName && + typeToCheck.expectedTypeName !== typeToCheck.currentType.name) { context.report({ node: jsdocNode, - message: "Use '{{expectedType}}' instead of '{{currentType}}'.", + message: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.", + loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType), data: { - currentType: typeToCheck.currentType, - expectedType: typeToCheck.expectedType + currentTypeName: typeToCheck.currentType.name, + expectedTypeName: typeToCheck.expectedTypeName + }, + fix(fixer) { + return fixer.replaceTextRange( + typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment), + typeToCheck.expectedTypeName + ); } }); } @@ -227,8 +248,8 @@ module.exports = { function checkJSDoc(node) { const jsdocNode = sourceCode.getJSDocComment(node), functionData = fns.pop(), - params = Object.create(null), - paramsTags = []; + paramTagsByName = Object.create(null), + paramTags = []; let hasReturns = false, returnsTag, hasConstructor = false, @@ -244,7 +265,8 @@ module.exports = { jsdoc = doctrine.parse(jsdocNode.value, { strict: true, unwrap: true, - sloppy: true + sloppy: true, + range: true }); } catch (ex) { @@ -264,7 +286,7 @@ module.exports = { case "param": case "arg": case "argument": - paramsTags.push(tag); + paramTags.push(tag); break; case "return": @@ -297,7 +319,29 @@ module.exports = { // check tag preferences if (prefer.hasOwnProperty(tag.title) && tag.title !== prefer[tag.title]) { - context.report({ node: jsdocNode, message: "Use @{{name}} instead.", data: { name: prefer[tag.title] } }); + const entireTagRange = getAbsoluteRange(jsdocNode, tag); + + context.report({ + node: jsdocNode, + message: "Use @{{name}} instead.", + loc: { + start: entireTagRange.start, + end: { + line: entireTagRange.start.line, + column: entireTagRange.start.column + `@${tag.title}`.length + } + }, + data: { name: prefer[tag.title] }, + fix(fixer) { + return fixer.replaceTextRange( + [ + jsdocNode.range[0] + tag.range[0] + 3, + jsdocNode.range[0] + tag.range[0] + tag.title.length + 3 + ], + prefer[tag.title] + ); + } + }); } // validate the types @@ -306,17 +350,32 @@ module.exports = { } }); - paramsTags.forEach(param => { + paramTags.forEach(param => { if (!param.type) { - context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: param.name } }); + context.report({ + node: jsdocNode, + message: "Missing JSDoc parameter type for '{{name}}'.", + loc: getAbsoluteRange(jsdocNode, param), + data: { name: param.name } + }); } if (!param.description && requireParamDescription) { - context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: param.name } }); + context.report({ + node: jsdocNode, + message: "Missing JSDoc parameter description for '{{name}}'.", + loc: getAbsoluteRange(jsdocNode, param), + data: { name: param.name } + }); } - if (params[param.name]) { - context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: param.name } }); + if (paramTagsByName[param.name]) { + context.report({ + node: jsdocNode, + message: "Duplicate JSDoc parameter '{{name}}'.", + loc: getAbsoluteRange(jsdocNode, param), + data: { name: param.name } + }); } else if (param.name.indexOf(".") === -1) { - params[param.name] = 1; + paramTagsByName[param.name] = param; } }); @@ -325,6 +384,7 @@ module.exports = { context.report({ node: jsdocNode, message: "Unexpected @{{title}} tag; function has no return statement.", + loc: getAbsoluteRange(jsdocNode, returnsTag), data: { title: returnsTag.title } @@ -356,28 +416,29 @@ module.exports = { } // check the parameters - const jsdocParams = Object.keys(params); + const jsdocParamNames = Object.keys(paramTagsByName); if (node.params) { - node.params.forEach((param, i) => { - if (param.type === "AssignmentPattern") { - param = param.left; - } - - const name = param.name; + node.params.forEach((param, paramsIndex) => { + const bindingParam = param.type === "AssignmentPattern" + ? param.left + : param; // TODO(nzakas): Figure out logical things to do with destructured, default, rest params - if (param.type === "Identifier") { - if (jsdocParams[i] && (name !== jsdocParams[i])) { + if (bindingParam.type === "Identifier") { + const name = bindingParam.name; + + if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) { context.report({ node: jsdocNode, message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", + loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]), data: { name, - jsdocName: jsdocParams[i] + jsdocName: jsdocParamNames[paramsIndex] } }); - } else if (!params[name] && !isOverride) { + } else if (!paramTagsByName[name] && !isOverride) { context.report({ node: jsdocNode, message: "Missing JSDoc for parameter '{{name}}'.", diff --git a/tools/node_modules/eslint/lib/rules/vars-on-top.js b/tools/node_modules/eslint/lib/rules/vars-on-top.js index 8f6bf1d977..0489aa61fc 100644 --- a/tools/node_modules/eslint/lib/rules/vars-on-top.js +++ b/tools/node_modules/eslint/lib/rules/vars-on-top.js @@ -125,23 +125,13 @@ module.exports = { //-------------------------------------------------------------------------- return { - VariableDeclaration(node) { - const ancestors = context.getAncestors(); - let parent = ancestors.pop(); - let grandParent = ancestors.pop(); - - if (node.kind === "var") { // check variable is `var` type and not `let` or `const` - if (parent.type === "ExportNamedDeclaration") { - node = parent; - parent = grandParent; - grandParent = ancestors.pop(); - } - - if (parent.type === "Program") { // That means its a global variable - globalVarCheck(node, parent); - } else { - blockScopeVarCheck(node, parent, grandParent); - } + "VariableDeclaration[kind='var']"(node) { + if (node.parent.type === "ExportNamedDeclaration") { + globalVarCheck(node.parent, node.parent.parent); + } else if (node.parent.type === "Program") { + globalVarCheck(node, node.parent); + } else { + blockScopeVarCheck(node, node.parent, node.parent.parent); } } }; diff --git a/tools/node_modules/eslint/lib/timing.js b/tools/node_modules/eslint/lib/timing.js index e33ac8f458..9452e419b1 100644 --- a/tools/node_modules/eslint/lib/timing.js +++ b/tools/node_modules/eslint/lib/timing.js @@ -90,12 +90,10 @@ function display(data) { .join(" | ") )); - table.splice(1, 0, widths.map((w, index) => { - if (index !== 0 && index !== widths.length - 1) { - w++; - } + table.splice(1, 0, widths.map((width, index) => { + const extraAlignment = index !== 0 && index !== widths.length - 1 ? 2 : 1; - return ALIGN[index](":", w + 1, "-"); + return ALIGN[index](":", width + extraAlignment, "-"); }).join("|")); console.log(table.join("\n")); // eslint-disable-line no-console diff --git a/tools/node_modules/eslint/lib/util/glob-util.js b/tools/node_modules/eslint/lib/util/glob-util.js index 6a1f150a59..d687aa7462 100644 --- a/tools/node_modules/eslint/lib/util/glob-util.js +++ b/tools/node_modules/eslint/lib/util/glob-util.js @@ -8,7 +8,8 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"), +const lodash = require("lodash"), + fs = require("fs"), path = require("path"), GlobSync = require("./glob"), @@ -88,24 +89,31 @@ function resolveFileGlobPatterns(patterns, options) { return patterns.filter(p => p.length).map(processPathExtensions); } +const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/; + /** * Build a list of absolute filesnames on which ESLint will act. * Ignored files are excluded from the results, as are duplicates. * - * @param {string[]} globPatterns Glob patterns. - * @param {Object} [options] An options object. - * @param {string} [options.cwd] CWD (considered for relative filenames) - * @param {boolean} [options.ignore] False disables use of .eslintignore. - * @param {string} [options.ignorePath] The ignore file to use instead of .eslintignore. - * @param {string} [options.ignorePattern] A pattern of files to ignore. + * @param {string[]} globPatterns Glob patterns. + * @param {Object} [providedOptions] An options object. + * @param {string} [providedOptions.cwd] CWD (considered for relative filenames) + * @param {boolean} [providedOptions.ignore] False disables use of .eslintignore. + * @param {string} [providedOptions.ignorePath] The ignore file to use instead of .eslintignore. + * @param {string} [providedOptions.ignorePattern] A pattern of files to ignore. * @returns {string[]} Resolved absolute filenames. */ -function listFilesToProcess(globPatterns, options) { - options = options || { ignore: true }; - const files = [], - added = {}; +function listFilesToProcess(globPatterns, providedOptions) { + const options = providedOptions || { ignore: true }; + const files = []; + const added = {}; - const cwd = (options && options.cwd) || process.cwd(); + const cwd = options.cwd || process.cwd(); + + const getIgnorePaths = lodash.memoize( + optionsObj => + new IgnoredPaths(optionsObj) + ); /** * Executes the linter on a file defined by the `filename`. Skips @@ -151,15 +159,20 @@ function listFilesToProcess(globPatterns, options) { const file = path.resolve(cwd, pattern); if (fs.existsSync(file) && fs.statSync(file).isFile()) { - const ignoredPaths = new IgnoredPaths(options); + const ignoredPaths = getIgnorePaths(options); addFile(fs.realpathSync(file), true, ignoredPaths); } else { // regex to find .hidden or /.hidden patterns, but not ./relative or ../relative - const globIncludesDotfiles = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/.test(pattern); + const globIncludesDotfiles = dotfilesPattern.test(pattern); + let newOptions = options; + + if (!options.dotfiles) { + newOptions = Object.assign({}, options, { dotfiles: globIncludesDotfiles }); + } - const ignoredPaths = new IgnoredPaths(Object.assign({}, options, { dotfiles: options.dotfiles || globIncludesDotfiles })); + const ignoredPaths = getIgnorePaths(newOptions); const shouldIgnore = ignoredPaths.getIgnoredFoldersGlobChecker(); const globOptions = { nodir: true, diff --git a/tools/node_modules/eslint/lib/util/interpolate.js b/tools/node_modules/eslint/lib/util/interpolate.js index e0f2d027d1..cefdcca545 100644 --- a/tools/node_modules/eslint/lib/util/interpolate.js +++ b/tools/node_modules/eslint/lib/util/interpolate.js @@ -13,7 +13,11 @@ module.exports = (text, data) => { if (!data) { return text; } - return text.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => { + + // Substitution content for any {{ }} markers. + return text.replace(/\{\{([^{}]+?)\}\}/g, (fullMatch, termWithWhitespace) => { + const term = termWithWhitespace.trim(); + if (term in data) { return data[term]; } diff --git a/tools/node_modules/eslint/lib/util/naming.js b/tools/node_modules/eslint/lib/util/naming.js index dcac81bbd6..c5ff429aa4 100644 --- a/tools/node_modules/eslint/lib/util/naming.js +++ b/tools/node_modules/eslint/lib/util/naming.js @@ -23,17 +23,18 @@ const NAMESPACE_REGEX = /^@.*\//i; * @private */ function normalizePackageName(name, prefix) { + let normalizedName = name; /** * On Windows, name can come in with Windows slashes instead of Unix slashes. * Normalize to Unix first to avoid errors later on. * https://github.com/eslint/eslint/issues/5644 */ - if (name.indexOf("\\") > -1) { - name = pathUtil.convertPathToPosix(name); + if (normalizedName.indexOf("\\") > -1) { + normalizedName = pathUtil.convertPathToPosix(normalizedName); } - if (name.charAt(0) === "@") { + if (normalizedName.charAt(0) === "@") { /** * it's a scoped package @@ -42,21 +43,21 @@ function normalizePackageName(name, prefix) { const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`), scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`); - if (scopedPackageShortcutRegex.test(name)) { - name = name.replace(scopedPackageShortcutRegex, `$1/${prefix}`); - } else if (!scopedPackageNameRegex.test(name.split("/")[1])) { + if (scopedPackageShortcutRegex.test(normalizedName)) { + normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`); + } else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) { /** * for scoped packages, insert the prefix after the first / unless * the path is already @scope/eslint or @scope/eslint-xxx-yyy */ - name = name.replace(/^@([^/]+)\/(.*)$/, `@$1/${prefix}-$2`); + normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/, `@$1/${prefix}-$2`); } - } else if (name.indexOf(`${prefix}-`) !== 0) { - name = `${prefix}-${name}`; + } else if (normalizedName.indexOf(`${prefix}-`) !== 0) { + normalizedName = `${prefix}-${normalizedName}`; } - return name; + return normalizedName; } /** diff --git a/tools/node_modules/eslint/lib/util/npm-util.js b/tools/node_modules/eslint/lib/util/npm-util.js index 6c431e0395..0b21f62613 100644 --- a/tools/node_modules/eslint/lib/util/npm-util.js +++ b/tools/node_modules/eslint/lib/util/npm-util.js @@ -50,17 +50,15 @@ function findPackageJson(startDir) { * @returns {void} */ function installSyncSaveDev(packages) { - if (!Array.isArray(packages)) { - packages = [packages]; - } - const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packages), + const packageList = Array.isArray(packages) ? packages : [packages]; + const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), { stdio: "inherit" }); const error = npmProcess.error; if (error && error.code === "ENOENT") { - const pluralS = packages.length > 1 ? "s" : ""; + const pluralS = packageList.length > 1 ? "s" : ""; - log.error(`Could not execute npm. Please install the following package${pluralS} with your package manager of choice: ${packages.join(", ")}`); + log.error(`Could not execute npm. Please install the following package${pluralS} with a package manager of your choice: ${packageList.join(", ")}`); } } diff --git a/tools/node_modules/eslint/lib/util/path-util.js b/tools/node_modules/eslint/lib/util/path-util.js index 4100ff91a0..54460ffd1f 100644 --- a/tools/node_modules/eslint/lib/util/path-util.js +++ b/tools/node_modules/eslint/lib/util/path-util.js @@ -48,20 +48,18 @@ function convertPathToPosix(filepath) { * @returns {string} Relative filepath */ function getRelativePath(filepath, baseDir) { - let relativePath; + const absolutePath = path.isAbsolute(filepath) + ? filepath + : path.resolve(filepath); - if (!path.isAbsolute(filepath)) { - filepath = path.resolve(filepath); - } if (baseDir) { if (!path.isAbsolute(baseDir)) { throw new Error("baseDir should be an absolute path"); } - relativePath = path.relative(baseDir, filepath); - } else { - relativePath = filepath.replace(/^\//, ""); + return path.relative(baseDir, absolutePath); } - return relativePath; + return absolutePath.replace(/^\//, ""); + } //------------------------------------------------------------------------------ diff --git a/tools/node_modules/eslint/lib/util/source-code-util.js b/tools/node_modules/eslint/lib/util/source-code-util.js index 6ffd243e2e..815fad9112 100644 --- a/tools/node_modules/eslint/lib/util/source-code-util.js +++ b/tools/node_modules/eslint/lib/util/source-code-util.js @@ -55,50 +55,47 @@ function getSourceCodeOfFile(filename, options) { /** * Gets the SourceCode of a single file, or set of files. - * @param {string[]|string} patterns A filename, directory name, or glob, - * or an array of them - * @param {Object} [options] A CLIEngine options object. If not provided, - * the default cli options will be used. - * @param {progressCallback} [cb] Callback for reporting execution status - * @returns {Object} The SourceCode of all processed files. + * @param {string[]|string} patterns A filename, directory name, or glob, or an array of them + * @param {Object} [providedOptions] A CLIEngine options object. If not provided, the default cli options will be used. + * @param {progressCallback} [providedCallback] Callback for reporting execution status + * @returns {Object} The SourceCode of all processed files. */ -function getSourceCodeOfFiles(patterns, options, cb) { +function getSourceCodeOfFiles(patterns, providedOptions, providedCallback) { const sourceCodes = {}; - let opts; - - if (typeof patterns === "string") { - patterns = [patterns]; - } + const globPatternsList = typeof patterns === "string" ? [patterns] : patterns; + let options, callback; const defaultOptions = Object.assign({}, baseDefaultOptions, { cwd: process.cwd() }); - if (typeof options === "undefined") { - opts = defaultOptions; - } else if (typeof options === "function") { - cb = options; - opts = defaultOptions; - } else if (typeof options === "object") { - opts = Object.assign({}, defaultOptions, options); + if (typeof providedOptions === "undefined") { + options = defaultOptions; + callback = null; + } else if (typeof providedOptions === "function") { + callback = providedOptions; + options = defaultOptions; + } else if (typeof providedOptions === "object") { + options = Object.assign({}, defaultOptions, providedOptions); + callback = providedCallback; } - debug("constructed options:", opts); - patterns = globUtil.resolveFileGlobPatterns(patterns, opts); + debug("constructed options:", options); + const resolvedPatterns = globUtil.resolveFileGlobPatterns(globPatternsList, options); - const filenames = globUtil.listFilesToProcess(patterns, opts) + const filenames = globUtil.listFilesToProcess(resolvedPatterns, options) .filter(fileInfo => !fileInfo.ignored) .reduce((files, fileInfo) => files.concat(fileInfo.filename), []); if (filenames.length === 0) { - debug(`Did not find any files matching pattern(s): ${patterns}`); + debug(`Did not find any files matching pattern(s): ${resolvedPatterns}`); } filenames.forEach(filename => { - const sourceCode = getSourceCodeOfFile(filename, opts); + const sourceCode = getSourceCodeOfFile(filename, options); if (sourceCode) { debug("got sourceCode of", filename); sourceCodes[filename] = sourceCode; } - if (cb) { - cb(filenames.length); // eslint-disable-line callback-return + if (callback) { + callback(filenames.length); // eslint-disable-line callback-return } }); return sourceCodes; diff --git a/tools/node_modules/eslint/lib/util/source-code.js b/tools/node_modules/eslint/lib/util/source-code.js index dee81aa10c..3375f4483d 100644 --- a/tools/node_modules/eslint/lib/util/source-code.js +++ b/tools/node_modules/eslint/lib/util/source-code.js @@ -90,15 +90,16 @@ class SourceCode extends TokenStore { * @param {Object|null} textOrConfig.parserServices - The parser srevices. * @param {ScopeManager|null} textOrConfig.scopeManager - The scope of this source code. * @param {Object|null} textOrConfig.visitorKeys - The visitor keys to traverse AST. - * @param {ASTNode} [ast] - The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. + * @param {ASTNode} [astIfNoConfig] - The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. * @constructor */ - constructor(textOrConfig, ast) { - let text, parserServices, scopeManager, visitorKeys; + constructor(textOrConfig, astIfNoConfig) { + let text, ast, parserServices, scopeManager, visitorKeys; // Process overloading. if (typeof textOrConfig === "string") { text = textOrConfig; + ast = astIfNoConfig; } else if (typeof textOrConfig === "object" && textOrConfig !== null) { text = textOrConfig.text; ast = textOrConfig.ast; |