diff --git a/Makefile.js b/Makefile.js index 02363aa6641..0f92dc12113 100644 --- a/Makefile.js +++ b/Makefile.js @@ -345,9 +345,10 @@ function getFirstVersionOfFile(filePath) { tags = splitCommandResultToLines(tags); return tags.reduce((list, version) => { - version = semver.valid(version.trim()); - if (version) { - list.push(version); + const validatedVersion = semver.valid(version.trim()); + + if (validatedVersion) { + list.push(validatedVersion); } return list; }, []).sort(semver.compare)[0]; diff --git a/lib/ast-utils.js b/lib/ast-utils.js index a186bdee54d..a188c7fa1c6 100644 --- a/lib/ast-utils.js +++ b/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/lib/cli-engine.js b/lib/cli-engine.js index 0c1afcbcebd..8531d1c1d4f 100644 --- a/lib/cli-engine.js +++ b/lib/cli-engine.js @@ -157,8 +157,9 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, repor fileExtension = path.extname(filename); } - filename = filename || ""; - debug(`Linting ${filename}`); + const effectiveFilename = filename || ""; + + 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/lib/code-path-analysis/code-path-state.js b/lib/code-path-analysis/code-path-state.js index 0c31e2072b0..57da10fa915 100644 --- a/lib/code-path-analysis/code-path-state.js +++ b/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/lib/code-path-analysis/code-path.js b/lib/code-path-analysis/code-path.js index 709a1111890..cb26ea18a3e 100644 --- a/lib/code-path-analysis/code-path.js +++ b/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/lib/code-path-analysis/fork-context.js b/lib/code-path-analysis/fork-context.js index 4fae6bbb1e8..939ed2d0d9a 100644 --- a/lib/code-path-analysis/fork-context.js +++ b/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/lib/config.js b/lib/config.js index b66b9f41e0d..7ba5cd6e2d1 100644 --- a/lib/config.js +++ b/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/lib/config/config-file.js b/lib/config/config-file.js index c5ff073cfcb..37ac84831a2 100644 --- a/lib/config/config-file.js +++ b/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/lib/config/config-ops.js b/lib/config/config-ops.js index 2ce500a4f47..67c23a8a613 100644 --- a/lib/config/config-ops.js +++ b/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/lib/config/config-rule.js b/lib/config/config-rule.js index 5fc38ac5d17..27d29446dbb 100644 --- a/lib/config/config-rule.js +++ b/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/lib/file-finder.js b/lib/file-finder.js index 3458bbf52a4..11091a99d57 100644 --- a/lib/file-finder.js +++ b/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/lib/ignored-paths.js b/lib/ignored-paths.js index c02e83bc2a5..8fff260d02d 100644 --- a/lib/ignored-paths.js +++ b/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/lib/linter.js b/lib/linter.js index ae8706d1264..8c631522f9d 100644 --- a/lib/linter.js +++ b/lib/linter.js @@ -71,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; } @@ -127,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, @@ -138,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 } @@ -161,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; } @@ -362,7 +361,7 @@ function normalizeEcmaVersion(ecmaVersion, isModule) { // Need at least ES6 for modules if (isModule && (!ecmaVersion || ecmaVersion < 6)) { - ecmaVersion = 6; + return 6; } /* @@ -370,7 +369,7 @@ function normalizeEcmaVersion(ecmaVersion, isModule) { * ES2015, which corresponds with ES6 (or a difference of 2009). */ if (ecmaVersion >= 2015) { - ecmaVersion -= 2009; + return ecmaVersion - 2009; } return ecmaVersion; @@ -1118,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; @@ -1135,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. @@ -1152,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 && @@ -1164,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/lib/load-rules.js b/lib/load-rules.js index b74905d65a5..a9da956bddc 100644 --- a/lib/load-rules.js +++ b/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/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js index 51ac2c2183a..68607295438 100644 --- a/lib/rules/accessor-pairs.js +++ b/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") ); } diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index ad0d02697f1..2375dcba9b1 100644 --- a/lib/rules/array-callback-return.js +++ b/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; diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js index d8a8652dbe0..359b8d436ef 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -59,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; diff --git a/lib/rules/curly.js b/lib/rules/curly.js index f1e4f49c63a..07d991b31a4 100644 --- a/lib/rules/curly.js +++ b/lib/rules/curly.js @@ -137,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); } } @@ -311,14 +313,13 @@ module.exports = { function prepareIfChecks(node) { const preparedChecks = []; - do { - preparedChecks.push(prepareCheck(node, node.consequent, "if", { condition: true })); - 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) { diff --git a/lib/rules/generator-star-spacing.js b/lib/rules/generator-star-spacing.js index 282b37510b1..68f2863626a 100644 --- a/lib/rules/generator-star-spacing.js +++ b/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/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js index 701cf016324..dc6d1689e47 100644 --- a/lib/rules/indent-legacy.js +++ b/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/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index 89b97332587..a2ce79ab5ae 100644 --- a/lib/rules/keyword-spacing.js +++ b/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); } /** diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 5a6eaa72f1e..273eb849078 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -288,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 @@ -306,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] @@ -321,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/lib/rules/no-else-return.js b/lib/rules/no-else-return.js index 015ceddc4fc..5e58acfe058 100644 --- a/lib/rules/no-else-return.js +++ b/lib/rules/no-else-return.js @@ -217,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 @@ -228,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/lib/rules/no-eval.js b/lib/rules/no-eval.js index f77451ec381..68ed086a3d9 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -151,18 +151,17 @@ 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, messageId: "unexpected" }); diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index daf069e9b9f..9765dfa7779 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -167,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; diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js index 7efab83935f..1dd4d431d72 100644 --- a/lib/rules/no-implicit-coercion.js +++ b/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/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index 0dce09a61a4..d103cb53350 100644 --- a/lib/rules/no-loop-func.js +++ b/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/lib/rules/no-magic-numbers.js b/lib/rules/no-magic-numbers.js index 20a752e554f..2826dbf493d 100644 --- a/lib/rules/no-magic-numbers.js +++ b/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/lib/rules/no-return-assign.js b/lib/rules/no-return-assign.js index 0a016cfad5e..ca96da9f2fa 100644 --- a/lib/rules/no-return-assign.js +++ b/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/lib/rules/no-unsafe-finally.js b/lib/rules/no-unsafe-finally.js index ebef05188fb..1ebdd2e3775 100644 --- a/lib/rules/no-unsafe-finally.js +++ b/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/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js index 8e2a6d97f67..d801c0e4650 100644 --- a/lib/rules/no-useless-return.js +++ b/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} [traversedSegments] A set of segments that have already been traversed in this call + * @param {WeakSet} [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/lib/rules/no-var.js b/lib/rules/no-var.js index d95ca539f03..5ca868e65a7 100644 --- a/lib/rules/no-var.js +++ b/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/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js index e03cec8ae74..d2254fa6ab6 100644 --- a/lib/rules/padding-line-between-statements.js +++ b/lib/rules/padding-line-between-statements.js @@ -473,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/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js index ff7a0fa7e7a..1bc140b101b 100644 --- a/lib/rules/prefer-arrow-callback.js +++ b/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/lib/rules/prefer-template.js b/lib/rules/prefer-template.js index c583bdcf9a2..2b893fd3715 100644 --- a/lib/rules/prefer-template.js +++ b/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/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js index 601d705e673..6fbcc15c040 100644 --- a/lib/rules/space-unary-ops.js +++ b/lib/rules/space-unary-ops.js @@ -141,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); @@ -276,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/lib/rules/spaced-comment.js b/lib/rules/spaced-comment.js index 9d2f5f49acd..3a76c0c2600 100644 --- a/lib/rules/spaced-comment.js +++ b/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/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js index c213c50f1b2..4038f70e567 100644 --- a/lib/rules/valid-jsdoc.js +++ b/lib/rules/valid-jsdoc.js @@ -420,14 +420,14 @@ module.exports = { if (node.params) { node.params.forEach((param, paramsIndex) => { - if (param.type === "AssignmentPattern") { - param = param.left; - } - - const name = param.name; + 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 (bindingParam.type === "Identifier") { + const name = bindingParam.name; + if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) { context.report({ node: jsdocNode, diff --git a/lib/rules/vars-on-top.js b/lib/rules/vars-on-top.js index 8f6bf1d9777..0489aa61fcc 100644 --- a/lib/rules/vars-on-top.js +++ b/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/lib/timing.js b/lib/timing.js index e33ac8f4589..9452e419b1b 100644 --- a/lib/timing.js +++ b/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/lib/util/glob-util.js b/lib/util/glob-util.js index e4b78270a47..d687aa74625 100644 --- a/lib/util/glob-util.js +++ b/lib/util/glob-util.js @@ -95,20 +95,20 @@ 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 => diff --git a/lib/util/naming.js b/lib/util/naming.js index dcac81bbd64..c5ff429aa49 100644 --- a/lib/util/naming.js +++ b/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/lib/util/npm-util.js b/lib/util/npm-util.js index 1c0cc5c6a67..0b21f626131 100644 --- a/lib/util/npm-util.js +++ b/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 a package manager of your 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/lib/util/path-util.js b/lib/util/path-util.js index 4100ff91a05..54460ffd1f9 100644 --- a/lib/util/path-util.js +++ b/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/lib/util/source-code-util.js b/lib/util/source-code-util.js index 6ffd243e2e1..815fad91122 100644 --- a/lib/util/source-code-util.js +++ b/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/lib/util/source-code.js b/lib/util/source-code.js index dee81aa10ca..3375f4483d4 100644 --- a/lib/util/source-code.js +++ b/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; diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml index f8f20d9e2a7..a2f9ed74283 100644 --- a/packages/eslint-config-eslint/default.yml +++ b/packages/eslint-config-eslint/default.yml @@ -85,6 +85,7 @@ rules: no-new-wrappers: "error" no-octal: "error" no-octal-escape: "error" + no-param-reassign: "error" no-path-concat: "error" no-process-exit: "error" no-proto: "error" diff --git a/tests/lib/formatters/html.js b/tests/lib/formatters/html.js index a3ccb5b1f8f..0f9c3d5e0a6 100644 --- a/tests/lib/formatters/html.js +++ b/tests/lib/formatters/html.js @@ -31,12 +31,13 @@ function checkOverview($, args) { /** * Run unit tests on the header section * @param {Object} $ Cheerio instance - * @param {Object} row Header row being tested + * @param {Object} rowObject Header row being tested * @param {Object} args Array of relevant info to be tested * @returns {void} */ -function checkHeaderRow($, row, args) { - row = $(row); +function checkHeaderRow($, rowObject, args) { + const row = $(rowObject); + assert(row.hasClass(args.bgColor), "Check that background color is correct"); assert.strictEqual(row.attr("data-group"), args.group, "Check that header group is correct"); assert.strictEqual(row.find("th span").text(), args.problems, "Check if correct totals"); @@ -46,12 +47,13 @@ function checkHeaderRow($, row, args) { /** * Run unit tests on the content section * @param {Object} $ Cheerio instance - * @param {Object} row Content row being tested + * @param {Object} rowObject Content row being tested * @param {Object} args Array of relevant info to be tested * @returns {void} */ -function checkContentRow($, row, args) { - row = $(row); +function checkContentRow($, rowObject, args) { + const row = $(rowObject); + assert(row.hasClass(args.group), "Check that linked to correct header"); assert.strictEqual($(row.find("td")[0]).text(), args.lineCol, "Check that line:column is correct"); assert($(row.find("td")[1]).hasClass(args.color), "Check that severity color is correct"); diff --git a/tests/lib/rules/indent-legacy.js b/tests/lib/rules/indent-legacy.js index d748a44a314..17a1f62a4c3 100644 --- a/tests/lib/rules/indent-legacy.js +++ b/tests/lib/rules/indent-legacy.js @@ -23,19 +23,21 @@ const fixedFixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/ /** * Create error message object for failure cases with a single 'found' indentation type - * @param {string} indentType indent type of string or tab - * @param {array} errors error info + * @param {string} providedIndentType indent type of string or tab + * @param {array} providedErrors error info * @returns {Object} returns the error messages collection * @private */ -function expectedErrors(indentType, errors) { - if (Array.isArray(indentType)) { - errors = indentType; - indentType = "space"; - } +function expectedErrors(providedIndentType, providedErrors) { + let indentType; + let errors; - if (!errors[0].length) { - errors = [errors]; + if (Array.isArray(providedIndentType)) { + errors = Array.isArray(providedIndentType[0]) ? providedIndentType : [providedIndentType]; + indentType = "space"; + } else { + errors = Array.isArray(providedErrors[0]) ? providedErrors : [providedErrors]; + indentType = providedIndentType; } return errors.map(err => { diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index f8914a68529..ab8ba4c18b0 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -24,19 +24,21 @@ const parser = require("../../fixtures/fixture-parser"); /** * Create error message object for failure cases with a single 'found' indentation type - * @param {string} indentType indent type of string or tab - * @param {array} errors error info + * @param {string} providedIndentType indent type of string or tab + * @param {array} providedErrors error info * @returns {Object} returns the error messages collection * @private */ -function expectedErrors(indentType, errors) { - if (Array.isArray(indentType)) { - errors = indentType; - indentType = "space"; - } +function expectedErrors(providedIndentType, providedErrors) { + let indentType; + let errors; - if (!errors[0].length) { - errors = [errors]; + if (Array.isArray(providedIndentType)) { + errors = Array.isArray(providedIndentType[0]) ? providedIndentType : [providedIndentType]; + indentType = "space"; + } else { + errors = Array.isArray(providedErrors[0]) ? providedErrors : [providedErrors]; + indentType = providedIndentType; } return errors.map(err => { diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index 93d669f88ee..10d30ce2c03 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -23,19 +23,17 @@ const rule = require("../../../lib/rules/no-extra-parens"), * @private */ function invalid(code, output, type, line, config) { - config = config || {}; - const result = { code, output, - parserOptions: config.parserOptions || {}, + parserOptions: config && config.parserOptions || {}, errors: [ { messageId: "unexpected", type } ], - options: config.options || [] + options: config && config.options || [] }; if (line) { diff --git a/tests/lib/rules/no-multiple-empty-lines.js b/tests/lib/rules/no-multiple-empty-lines.js index d4af791a7b1..4be2e14cff2 100644 --- a/tests/lib/rules/no-multiple-empty-lines.js +++ b/tests/lib/rules/no-multiple-empty-lines.js @@ -42,10 +42,6 @@ function getExpectedError(lines) { * @private */ function getExpectedErrorEOF(lines) { - if (typeof lines !== "number") { - lines = 0; - } - return { message: `Too many blank lines at the end of file. Max of ${lines} allowed.`, type: "Program", @@ -60,10 +56,6 @@ function getExpectedErrorEOF(lines) { * @private */ function getExpectedErrorBOF(lines) { - if (typeof lines !== "number") { - lines = 0; - } - return { message: `Too many blank lines at the beginning of file. Max of ${lines} allowed.`, type: "Program",