Bump packages to fix linter

This commit is contained in:
Henry Mercer 2023-01-18 20:50:03 +00:00
parent ed9506bbaf
commit 0a11e3fdd9
6063 changed files with 378752 additions and 306784 deletions

24
node_modules/eslint/lib/api.js generated vendored
View file

@ -5,30 +5,22 @@
"use strict";
const { CLIEngine } = require("./cli-engine");
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { ESLint } = require("./eslint");
const { Linter } = require("./linter");
const { RuleTester } = require("./rule-tester");
const { SourceCode } = require("./source-code");
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
module.exports = {
Linter,
CLIEngine,
ESLint,
RuleTester,
SourceCode
};
// DOTO: remove deprecated API.
let deprecatedLinterInstance = null;
Object.defineProperty(module.exports, "linter", {
enumerable: false,
get() {
if (!deprecatedLinterInstance) {
deprecatedLinterInstance = new Linter();
}
return deprecatedLinterInstance;
}
});

View file

@ -41,7 +41,7 @@ const hash = require("./hash");
const LintResultCache = require("./lint-result-cache");
const debug = require("debug")("eslint:cli-engine");
const validFixTypes = new Set(["problem", "suggestion", "layout"]);
const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
//------------------------------------------------------------------------------
// Typedefs
@ -51,12 +51,14 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
/** @typedef {import("../shared/types").ConfigData} ConfigData */
/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
/** @typedef {import("../shared/types").LintMessage} LintMessage */
/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
/** @typedef {import("../shared/types").ParserOptions} ParserOptions */
/** @typedef {import("../shared/types").Plugin} Plugin */
/** @typedef {import("../shared/types").RuleConf} RuleConf */
/** @typedef {import("../shared/types").Rule} Rule */
/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
/** @typedef {ReturnType<ConfigArray["extractConfig"]>} ExtractedConfig */
/** @typedef {import("../shared/types").FormatterFunction} FormatterFunction */
/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
/**
* The options to configure a CLI engine with.
@ -91,7 +93,9 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @typedef {Object} LintResult
* @property {string} filePath The path to the file that was linted.
* @property {LintMessage[]} messages All of the messages for the result.
* @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
* @property {number} errorCount Number of errors for the result.
* @property {number} fatalErrorCount Number of fatal errors for the result.
* @property {number} warningCount Number of warnings for the result.
* @property {number} fixableErrorCount Number of fixable errors for the result.
* @property {number} fixableWarningCount Number of fixable warnings for the result.
@ -104,6 +108,7 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @typedef {Object} LintReport
* @property {LintResult[]} results All of the result.
* @property {number} errorCount Number of errors for the result.
* @property {number} fatalErrorCount Number of fatal errors for the result.
* @property {number} warningCount Number of warnings for the result.
* @property {number} fixableErrorCount Number of fixable errors for the result.
* @property {number} fixableWarningCount Number of fixable warnings for the result.
@ -261,6 +266,7 @@ function verifyText({
const result = {
filePath,
messages,
suppressedMessages: linter.getSuppressedMessages(),
...calculateStatsPerFile(messages)
};
@ -280,7 +286,7 @@ function verifyText({
/**
* Returns result with warning by ignore settings
* @param {string} filePath File path of checked code
* @param {string} baseDir Absolute path of base directory
* @param {string} baseDir Absolute path of base directory
* @returns {LintResult} Result with single warning
* @private
*/
@ -307,7 +313,9 @@ function createIgnoreResult(filePath, baseDir) {
message
}
],
suppressedMessages: [],
errorCount: 0,
fatalErrorCount: 0,
warningCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0
@ -331,6 +339,23 @@ function getRule(ruleId, configArrays) {
return builtInRules.get(ruleId) || null;
}
/**
* Checks whether a message's rule type should be fixed.
* @param {LintMessage} message The message to check.
* @param {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
* @param {string[]} fixTypes An array of fix types to check.
* @returns {boolean} Whether the message should be fixed.
*/
function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) {
if (!message.ruleId) {
return fixTypes.has("directive");
}
const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
}
/**
* Collect used deprecated rules.
* @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
@ -341,9 +366,7 @@ function *iterateRuleDeprecationWarnings(usedConfigArrays) {
// Flatten used configs.
/** @type {ExtractedConfig[]} */
const configs = [].concat(
...usedConfigArrays.map(getUsedExtractedConfigs)
);
const configs = usedConfigArrays.flatMap(getUsedExtractedConfigs);
// Traverse rule configs.
for (const config of configs) {
@ -391,7 +414,7 @@ function isErrorMessage(message) {
* a directory or looks like a directory (ends in `path.sep`), in which case the file
* name will be the `cacheFile/.cache_hashOfCWD`
*
* if cacheFile points to a file or looks like a file then in will just use that file
* if cacheFile points to a file or looks like a file then it will just use that file
* @param {string} cacheFile The name of file to be used to store the cache
* @param {string} cwd Current working directory
* @returns {string} the resolved path to the cache file
@ -463,6 +486,7 @@ function getCacheFile(cacheFile, cwd) {
* @param {string[]|null} keys The keys to assign true.
* @param {boolean} defaultValue The default value for each property.
* @param {string} displayName The property name which is used in error message.
* @throws {Error} Requires array.
* @returns {Record<string,boolean>} The boolean map.
*/
function toBooleanMap(keys, defaultValue, displayName) {
@ -526,6 +550,7 @@ function createConfigDataFromOptions(options) {
/**
* Checks whether a directory exists at the given location
* @param {string} resolvedPath A path from the CWD
* @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`.
* @returns {boolean} `true` if a directory exists
*/
function directoryExists(resolvedPath) {
@ -543,13 +568,18 @@ function directoryExists(resolvedPath) {
// Public Interface
//------------------------------------------------------------------------------
/**
* Core CLI.
*/
class CLIEngine {
/**
* Creates a new instance of the core CLI engine.
* @param {CLIEngineOptions} providedOptions The options for this instance.
* @param {Object} [additionalData] Additional settings that are not CLIEngineOptions.
* @param {Record<string,Plugin>|null} [additionalData.preloadedPlugins] Preloaded plugins.
*/
constructor(providedOptions) {
constructor(providedOptions, { preloadedPlugins } = {}) {
const options = Object.assign(
Object.create(null),
defaultOptions,
@ -562,6 +592,13 @@ class CLIEngine {
}
const additionalPluginPool = new Map();
if (preloadedPlugins) {
for (const [id, plugin] of Object.entries(preloadedPlugins)) {
additionalPluginPool.set(id, plugin);
}
}
const cacheFilePath = getCacheFile(
options.cacheLocation || options.cacheFile,
options.cwd
@ -578,8 +615,8 @@ class CLIEngine {
useEslintrc: options.useEslintrc,
builtInRules,
loadRules,
eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"),
getEslintAllConfig: () => require("../../conf/eslint-all.js")
});
const fileEnumerator = new FileEnumerator({
configArrayFactory,
@ -623,12 +660,7 @@ class CLIEngine {
const originalFix = (typeof options.fix === "function")
? options.fix : () => true;
options.fix = message => {
const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
const matches = rule && rule.meta && fixTypes.has(rule.meta.type);
return matches && originalFix(message);
};
options.fix = message => shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && originalFix(message);
}
}
@ -654,11 +686,13 @@ class CLIEngine {
results.forEach(result => {
const filteredMessages = result.messages.filter(isErrorMessage);
const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage);
if (filteredMessages.length > 0) {
filtered.push({
...result,
messages: filteredMessages,
suppressedMessages: filteredSuppressedMessages,
errorCount: filteredMessages.length,
warningCount: 0,
fixableErrorCount: result.fixableErrorCount,
@ -681,26 +715,6 @@ class CLIEngine {
});
}
/**
* Add a plugin by passing its configuration
* @param {string} name Name of the plugin.
* @param {Plugin} pluginObject Plugin configuration object.
* @returns {void}
*/
addPlugin(name, pluginObject) {
const {
additionalPluginPool,
configArrayFactory,
lastConfigArrays
} = internalSlotsMap.get(this);
additionalPluginPool.set(name, pluginObject);
configArrayFactory.clearCache();
lastConfigArrays.length = 1;
lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
}
/**
* Resolves the patterns passed into executeOnFiles() into glob-based patterns
* for easier handling.
@ -730,6 +744,7 @@ class CLIEngine {
/**
* Executes the current configuration on an array of file and directory names.
* @param {string[]} patterns An array of file and directory names.
* @throws {Error} As may be thrown by `fs.unlinkSync`.
* @returns {LintReport} The results for all files that were linted.
*/
executeOnFiles(patterns) {
@ -936,6 +951,7 @@ class CLIEngine {
* This is the same logic used by the ESLint CLI executable to determine
* configuration for each file it processes.
* @param {string} filePath The path of the file to retrieve a config object for.
* @throws {Error} If filepath a directory path.
* @returns {ConfigData} A configuration object for the file.
*/
getConfigForFile(filePath) {
@ -984,7 +1000,8 @@ class CLIEngine {
* Returns the formatter representing the given format or null if the `format` is not a string.
* @param {string} [format] The name of the format to load or the path to a
* custom formatter.
* @returns {(Function|null)} The formatter function or null if the `format` is not a string.
* @throws {any} As may be thrown by requiring of formatter
* @returns {(FormatterFunction|null)} The formatter function or null if the `format` is not a string.
*/
getFormatter(format) {
@ -1004,7 +1021,7 @@ class CLIEngine {
let formatterPath;
// if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
if (!namespace && normalizedFormatName.indexOf("/") > -1) {
if (!namespace && normalizedFormatName.includes("/")) {
formatterPath = path.resolve(cwd, normalizedFormatName);
} else {
try {
@ -1019,7 +1036,11 @@ class CLIEngine {
try {
return require(formatterPath);
} catch (ex) {
ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
if (format === "table" || format === "codeframe") {
ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``;
} else {
ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
}
throw ex;
}

View file

@ -60,7 +60,7 @@ const IGNORED_SILENTLY = 1;
const IGNORED = 2;
// For VSCode intellisense
/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
/**
* @typedef {Object} FileEnumeratorOptions
@ -114,6 +114,7 @@ function isGlobPattern(pattern) {
/**
* Get stats of a given path.
* @param {string} filePath The path to target file.
* @throws {Error} As may be thrown by `fs.statSync`.
* @returns {fs.Stats|null} The stats.
* @private
*/
@ -121,7 +122,8 @@ function statSafeSync(filePath) {
try {
return fs.statSync(filePath);
} catch (error) {
/* istanbul ignore next */
/* c8 ignore next */
if (error.code !== "ENOENT") {
throw error;
}
@ -132,6 +134,7 @@ function statSafeSync(filePath) {
/**
* Get filenames in a given path to a directory.
* @param {string} directoryPath The path to target directory.
* @throws {Error} As may be thrown by `fs.readdirSync`.
* @returns {import("fs").Dirent[]} The filenames.
* @private
*/
@ -139,7 +142,8 @@ function readdirSafeSync(directoryPath) {
try {
return fs.readdirSync(directoryPath, { withFileTypes: true });
} catch (error) {
/* istanbul ignore next */
/* c8 ignore next */
if (error.code !== "ENOENT") {
throw error;
}
@ -173,7 +177,6 @@ function createExtensionRegExp(extensions) {
*/
class NoFilesFoundError extends Error {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {string} pattern The glob pattern which was not found.
* @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled.
@ -190,7 +193,6 @@ class NoFilesFoundError extends Error {
*/
class AllFilesIgnoredError extends Error {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {string} pattern The glob pattern which was not found.
*/
@ -215,8 +217,8 @@ class FileEnumerator {
cwd = process.cwd(),
configArrayFactory = new CascadingConfigArrayFactory({
cwd,
eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"),
getEslintAllConfig: () => require("../../conf/eslint-all.js")
}),
extensions = null,
globInputPaths = true,
@ -270,6 +272,7 @@ class FileEnumerator {
/**
* Iterate files which are matched by given glob patterns.
* @param {string|string[]} patternOrPatterns The glob patterns to iterate files.
* @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern.
* @returns {IterableIterator<FileAndConfig>} The found files.
*/
*iterateFiles(patternOrPatterns) {

View file

@ -1,138 +0,0 @@
/**
* @fileoverview Codeframe reporter
* @author Vitor Balocco
*/
"use strict";
const chalk = require("chalk");
const { codeFrameColumns } = require("@babel/code-frame");
const path = require("path");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Given a word and a count, append an s if count is not one.
* @param {string} word A word in its singular form.
* @param {number} count A number controlling whether word should be pluralized.
* @returns {string} The original word with an s on the end if count is not one.
*/
function pluralize(word, count) {
return (count === 1 ? word : `${word}s`);
}
/**
* Gets a formatted relative file path from an absolute path and a line/column in the file.
* @param {string} filePath The absolute file path to format.
* @param {number} line The line from the file to use for formatting.
* @param {number} column The column from the file to use for formatting.
* @returns {string} The formatted file path.
*/
function formatFilePath(filePath, line, column) {
let relPath = path.relative(process.cwd(), filePath);
if (line && column) {
relPath += `:${line}:${column}`;
}
return chalk.green(relPath);
}
/**
* Gets the formatted output for a given message.
* @param {Object} message The object that represents this message.
* @param {Object} parentResult The result object that this message belongs to.
* @returns {string} The formatted output.
*/
function formatMessage(message, parentResult) {
const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/u, "$1"))}`;
const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
const firstLine = [
`${type}:`,
`${msg}`,
ruleId ? `${ruleId}` : "",
sourceCode ? `at ${filePath}:` : `at ${filePath}`
].filter(String).join(" ");
const result = [firstLine];
if (sourceCode) {
result.push(
codeFrameColumns(sourceCode, { start: { line: message.line, column: message.column } }, { highlightCode: false })
);
}
return result.join("\n");
}
/**
* Gets the formatted output summary for a given number of errors and warnings.
* @param {number} errors The number of errors.
* @param {number} warnings The number of warnings.
* @param {number} fixableErrors The number of fixable errors.
* @param {number} fixableWarnings The number of fixable warnings.
* @returns {string} The formatted output summary.
*/
function formatSummary(errors, warnings, fixableErrors, fixableWarnings) {
const summaryColor = errors > 0 ? "red" : "yellow";
const summary = [];
const fixablesSummary = [];
if (errors > 0) {
summary.push(`${errors} ${pluralize("error", errors)}`);
}
if (warnings > 0) {
summary.push(`${warnings} ${pluralize("warning", warnings)}`);
}
if (fixableErrors > 0) {
fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`);
}
if (fixableWarnings > 0) {
fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`);
}
let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
if (fixableErrors || fixableWarnings) {
output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`);
}
return output;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function(results) {
let errors = 0;
let warnings = 0;
let fixableErrors = 0;
let fixableWarnings = 0;
const resultsWithMessages = results.filter(result => result.messages.length > 0);
let output = resultsWithMessages.reduce((resultsOutput, result) => {
const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);
errors += result.errorCount;
warnings += result.warningCount;
fixableErrors += result.fixableErrorCount;
fixableWarnings += result.fixableWarningCount;
return resultsOutput.concat(messages);
}, []).join("\n");
output += "\n";
output += formatSummary(errors, warnings, fixableErrors, fixableWarnings);
return (errors + warnings) > 0 ? output : "";
};

View file

@ -0,0 +1,46 @@
[
{
"name": "checkstyle",
"description": "Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format."
},
{
"name": "compact",
"description": "Human-readable output format. Mimics the default output of JSHint."
},
{
"name": "html",
"description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser."
},
{
"name": "jslint-xml",
"description": "Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/)."
},
{
"name": "json-with-metadata",
"description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
},
{
"name": "json",
"description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
},
{
"name": "junit",
"description": "Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/)."
},
{
"name": "stylish",
"description": "Human-readable output format. This is the default formatter."
},
{
"name": "tap",
"description": "Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format."
},
{
"name": "unix",
"description": "Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html)."
},
{
"name": "visualstudio",
"description": "Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code."
}
]

View file

@ -39,87 +39,114 @@ function pageTemplate(it) {
<head>
<meta charset="UTF-8">
<title>ESLint Report</title>
<link rel="icon" type="image/png" sizes="any" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAHaAAAB2gGFomX7AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAABD1JREFUWMPFl11sk2UUx3/nbYtjxS1MF7MLMTECMgSTtSSyrQkLhAj7UBPnDSEGoxegGzMwojhXVpmTAA5iYpSoMQa8GBhFOrMFk03buei6yRAlcmOM0SEmU9d90b19jxcM1o5+sGnsc/e+z/l6ztf/HFFVMnns6QieeOCHBePGsHM+wrOtvLG2C4WRVDSSygNV7sCjlspxwDnPB44aols/DXk+mbMBmx/6OseITF1CuOtfevkPh2Uu+/jbdX8lujSScRlT5r7/QDlAfsRmfzmpnkQ/H3H13gf6bBrBn1uqK8WylgEnU8eZmk1repbfchJG1TyKyIKEwuBHFd3lD3naY3O1siiwXsVoBV2VgM1ht/QQUJk2ByqKghsQziYQ8ifKgexIXmuyzC4r67Y7R+xPAfuB/Nn3Cpva+0s7khpQVtZtd4bt51BWxtBYAiciprG7c7D4SixzU9PYalDL6110Ifb/w8W9eY7JqFeFHbO8fPGyLHwwFHJNJTSgwtVTB9oaw9BlQ+tO93vOxypoaQnfEYlI43SeCHDC4TDq9+51/h5fxr33q0ZfV9g04wat9Q943rjJgCp3952W2i8Bi6eDvdsfKj0cK/DYMRyXL4/sUJUmIHd2zYMezsvLaamp4WpcWN3BXSiHpuMwbGbZlnZ8tXY4rgosy+G7oRwQ0cAsd28YGgqfU5UjCZQDLALxDg+Hv/P5Rqvj4hwrS8izXzWb4spwc1GgENFnkpWRzxeuB+ssUHgLdb9UVdt8vpGdKQpze7n7y1U3DBChNRUuqOo9c+0+qpKKxyZqtAIYla7gY4JszAAQri93BSsMRZoyBcUC+w3Q3AyOA4sNhAOZ0q7Iq0b2vUNvK5zPgP+/H8+Zetdoa6uOikhdGurxebwvJY8Iz3V1rTMNAH+opEuQj5KTT/qA1yC+wyUjBm12OidaUtCcPNNX2h0Hx2JG69VulANZAJZJwfU7rzd/FHixuXniTdM0m4GtSQT7bTartqEh9yfImUEzkwKZmTwmo5a5JwkYBfcDL01/RkR5y8iWhtPBknB8ZxwtU9UjwOrrKCeizzc25nTGg1F/turEHoU9wMLpDvWKf8DTmNCAKnd/tqUTF4ElMXJ+A5rWDJS+41WsGWzALhJ+ErBWrLj9g+pqojHxlXJX8HGUg0BsR/x1yhxf3jm4cSzpQFLp6tmi6PEE7g1ZhtZ91ufpSZUAFa6gC+UoQslNaSmypT1U8mHKiUgEKS8KfgF4EpYunFI16tsHin+OG0LcgQK7yj7g6cSzpva2D3hKVNG0Y3mVO1BkqfSlmJrHBQ4uvM12gJHc6ETW8HZVfMRmXvyxxNC1Z/o839zyXlDuCr4nsC11J+MXueaVJWn6yPv+/pJtc9oLTNN4AeTvNGByd3rlhE2x9s5pLwDoHCy+grDzWmOZ95lUtLYj5Bma126Y8eX0/zj/ADxGyViSg4BXAAAAAElFTkSuQmCC">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMjk0LjgyNSAyNTguOTgyJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPg0KPHBhdGggZmlsbD0nIzgwODBGMicgZD0nTTk3LjAyMSw5OS4wMTZsNDguNDMyLTI3Ljk2MmMxLjIxMi0wLjcsMi43MDYtMC43LDMuOTE4LDBsNDguNDMzLDI3Ljk2MiBjMS4yMTEsMC43LDEuOTU5LDEuOTkzLDEuOTU5LDMuMzkzdjU1LjkyNGMwLDEuMzk5LTAuNzQ4LDIuNjkzLTEuOTU5LDMuMzk0bC00OC40MzMsMjcuOTYyYy0xLjIxMiwwLjctMi43MDYsMC43LTMuOTE4LDAgbC00OC40MzItMjcuOTYyYy0xLjIxMi0wLjctMS45NTktMS45OTQtMS45NTktMy4zOTR2LTU1LjkyNEM5NS4wNjMsMTAxLjAwOSw5NS44MSw5OS43MTYsOTcuMDIxLDk5LjAxNicvPg0KPHBhdGggZmlsbD0nIzRCMzJDMycgZD0nTTI3My4zMzYsMTI0LjQ4OEwyMTUuNDY5LDIzLjgxNmMtMi4xMDItMy42NC01Ljk4NS02LjMyNS0xMC4xODgtNi4zMjVIODkuNTQ1IGMtNC4yMDQsMC04LjA4OCwyLjY4NS0xMC4xOSw2LjMyNWwtNTcuODY3LDEwMC40NWMtMi4xMDIsMy42NDEtMi4xMDIsOC4yMzYsMCwxMS44NzdsNTcuODY3LDk5Ljg0NyBjMi4xMDIsMy42NCw1Ljk4Niw1LjUwMSwxMC4xOSw1LjUwMWgxMTUuNzM1YzQuMjAzLDAsOC4wODctMS44MDUsMTAuMTg4LTUuNDQ2bDU3Ljg2Ny0xMDAuMDEgQzI3NS40MzksMTMyLjM5NiwyNzUuNDM5LDEyOC4xMjgsMjczLjMzNiwxMjQuNDg4IE0yMjUuNDE5LDE3Mi44OThjMCwxLjQ4LTAuODkxLDIuODQ5LTIuMTc0LDMuNTlsLTczLjcxLDQyLjUyNyBjLTEuMjgyLDAuNzQtMi44ODgsMC43NC00LjE3LDBsLTczLjc2Ny00Mi41MjdjLTEuMjgyLTAuNzQxLTIuMTc5LTIuMTA5LTIuMTc5LTMuNTlWODcuODQzYzAtMS40ODEsMC44ODQtMi44NDksMi4xNjctMy41OSBsNzMuNzA3LTQyLjUyN2MxLjI4Mi0wLjc0MSwyLjg4Ni0wLjc0MSw0LjE2OCwwbDczLjc3Miw0Mi41MjdjMS4yODMsMC43NDEsMi4xODYsMi4xMDksMi4xODYsMy41OVYxNzIuODk4eicvPg0KPC9zdmc+">
<style>
body {
font-family:Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size:16px;
font-weight:normal;
margin:0;
padding:0;
color:#333
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size: 16px;
font-weight: normal;
margin: 0;
padding: 0;
color: #333;
}
#overview {
padding:20px 30px
}
td, th {
padding:5px 10px
}
h1 {
margin:0
}
table {
margin:30px;
width:calc(100% - 60px);
max-width:1000px;
border-radius:5px;
border:1px solid #ddd;
border-spacing:0px;
padding: 20px 30px;
}
td,
th {
font-weight:400;
font-size:medium;
text-align:left;
cursor:pointer
padding: 5px 10px;
}
td.clr-1, td.clr-2, th span {
font-weight:700
h1 {
margin: 0;
}
table {
margin: 30px;
width: calc(100% - 60px);
max-width: 1000px;
border-radius: 5px;
border: 1px solid #ddd;
border-spacing: 0;
}
th {
font-weight: 400;
font-size: medium;
text-align: left;
cursor: pointer;
}
td.clr-1,
td.clr-2,
th span {
float:right;
margin-left:20px
font-weight: 700;
}
th span:after {
content:"";
clear:both;
display:block
th span {
float: right;
margin-left: 20px;
}
th span::after {
content: "";
clear: both;
display: block;
}
tr:last-child td {
border-bottom:none
border-bottom: none;
}
tr td:first-child, tr td:last-child {
color:#9da0a4
tr td:first-child,
tr td:last-child {
color: #9da0a4;
}
#overview.bg-0, tr.bg-0 th {
color:#468847;
background:#dff0d8;
border-bottom:1px solid #d6e9c6
#overview.bg-0,
tr.bg-0 th {
color: #468847;
background: #dff0d8;
border-bottom: 1px solid #d6e9c6;
}
#overview.bg-1, tr.bg-1 th {
color:#f0ad4e;
background:#fcf8e3;
border-bottom:1px solid #fbeed5
#overview.bg-1,
tr.bg-1 th {
color: #f0ad4e;
background: #fcf8e3;
border-bottom: 1px solid #fbeed5;
}
#overview.bg-2, tr.bg-2 th {
color:#b94a48;
background:#f2dede;
border-bottom:1px solid #eed3d7
#overview.bg-2,
tr.bg-2 th {
color: #b94a48;
background: #f2dede;
border-bottom: 1px solid #eed3d7;
}
td {
border-bottom:1px solid #ddd
border-bottom: 1px solid #ddd;
}
td.clr-1 {
color:#f0ad4e
color: #f0ad4e;
}
td.clr-2 {
color:#b94a48
color: #b94a48;
}
td a {
color:#3a33d1;
text-decoration:none
color: #3a33d1;
text-decoration: none;
}
td a:hover {
color:#272296;
text-decoration:underline
color: #272296;
text-decoration: underline;
}
</style>
</head>
@ -149,7 +176,7 @@ function pageTemplate(it) {
</script>
</body>
</html>
`.trimLeft();
`.trimStart();
}
/**
@ -212,7 +239,7 @@ function messageTemplate(it) {
} = it;
return `
<tr style="display:none" class="f-${parentIndex}">
<tr style="display: none;" class="f-${parentIndex}">
<td>${lineNumber}:${columnNumber}</td>
<td class="clr-${severityNumber}">${severityName}</td>
<td>${encodeHTML(message)}</td>
@ -220,7 +247,7 @@ function messageTemplate(it) {
<a href="${ruleUrl ? ruleUrl : ""}" target="_blank" rel="noopener noreferrer">${ruleId ? ruleId : ""}</a>
</td>
</tr>
`.trimLeft();
`.trimStart();
}
/**
@ -278,11 +305,11 @@ function resultTemplate(it) {
<span>${encodeHTML(summary)}</span>
</th>
</tr>
`.trimLeft();
`.trimStart();
}
// eslint-disable-next-line jsdoc/require-description
/**
* Render the results.
* @param {Array} results Test results.
* @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis.
* @returns {string} HTML string describing the results.

View file

@ -1,159 +0,0 @@
/**
* @fileoverview "table reporter.
* @author Gajus Kuizinas <gajus@gajus.com>
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const chalk = require("chalk"),
table = require("table").table;
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Given a word and a count, append an "s" if count is not one.
* @param {string} word A word.
* @param {number} count Quantity.
* @returns {string} The original word with an s on the end if count is not one.
*/
function pluralize(word, count) {
return (count === 1 ? word : `${word}s`);
}
/**
* Draws text table.
* @param {Array<Object>} messages Error messages relating to a specific file.
* @returns {string} A text table.
*/
function drawTable(messages) {
const rows = [];
if (messages.length === 0) {
return "";
}
rows.push([
chalk.bold("Line"),
chalk.bold("Column"),
chalk.bold("Type"),
chalk.bold("Message"),
chalk.bold("Rule ID")
]);
messages.forEach(message => {
let messageType;
if (message.fatal || message.severity === 2) {
messageType = chalk.red("error");
} else {
messageType = chalk.yellow("warning");
}
rows.push([
message.line || 0,
message.column || 0,
messageType,
message.message,
message.ruleId || ""
]);
});
return table(rows, {
columns: {
0: {
width: 8,
wrapWord: true
},
1: {
width: 8,
wrapWord: true
},
2: {
width: 8,
wrapWord: true
},
3: {
paddingRight: 5,
width: 50,
wrapWord: true
},
4: {
width: 20,
wrapWord: true
}
},
drawHorizontalLine(index) {
return index === 1;
}
});
}
/**
* Draws a report (multiple tables).
* @param {Array} results Report results for every file.
* @returns {string} A column of text tables.
*/
function drawReport(results) {
let files;
files = results.map(result => {
if (!result.messages.length) {
return "";
}
return `\n${result.filePath}\n\n${drawTable(result.messages)}`;
});
files = files.filter(content => content.trim());
return files.join("");
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function(report) {
let result,
errorCount,
warningCount;
result = "";
errorCount = 0;
warningCount = 0;
report.forEach(fileReport => {
errorCount += fileReport.errorCount;
warningCount += fileReport.warningCount;
});
if (errorCount || warningCount) {
result = drawReport(report);
}
result += `\n${table([
[
chalk.red(pluralize(`${errorCount} Error`, errorCount))
],
[
chalk.yellow(pluralize(`${warningCount} Warning`, warningCount))
]
], {
columns: {
0: {
width: 110,
wrapWord: true
}
},
drawHorizontalLine() {
return true;
}
})}`;
return result;
};

View file

@ -31,7 +31,7 @@ function outputDiagnostics(diagnostic) {
const prefix = " ";
let output = `${prefix}---\n`;
output += prefix + yaml.safeDump(diagnostic).split("\n").join(`\n${prefix}`);
output += prefix + yaml.dump(diagnostic).split("\n").join(`\n${prefix}`);
output += "...\n";
return output;
}

View file

@ -21,8 +21,8 @@ const murmur = require("imurmurhash");
/**
* hash the given string
* @param {string} str the string to hash
* @returns {string} the hash
* @param {string} str the string to hash
* @returns {string} the hash
*/
function hash(str) {
return murmur(str).result().toString(36);

View file

@ -36,7 +36,7 @@ const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${valid
*/
function isValidCacheStrategy(cacheStrategy) {
return (
validCacheStrategies.indexOf(cacheStrategy) !== -1
validCacheStrategies.includes(cacheStrategy)
);
}

View file

@ -15,7 +15,7 @@
* @private
*/
module.exports = function(s) {
return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex
return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex -- Converting controls to entities
switch (c) {
case "<":
return "&lt;";

203
node_modules/eslint/lib/cli.js generated vendored
View file

@ -6,7 +6,7 @@
"use strict";
/*
* The CLI object should *not* call process.exit() directly. It should only return
* NOTE: The CLI object should *not* call process.exit() directly. It should only return
* exit codes. This allows other programs to use the CLI object and still control
* when the program exits.
*/
@ -19,9 +19,13 @@ const fs = require("fs"),
path = require("path"),
{ promisify } = require("util"),
{ ESLint } = require("./eslint"),
CLIOptions = require("./options"),
{ FlatESLint } = require("./eslint/flat-eslint"),
createCLIOptions = require("./options"),
log = require("./shared/logging"),
RuntimeInfo = require("./shared/runtime-info");
const { Legacy: { naming } } = require("@eslint/eslintrc");
const { findFlatConfigFile } = require("./eslint/flat-eslint");
const { ModuleImporter } = require("@humanwhocodes/module-importer");
const debug = require("debug")("eslint:cli");
@ -33,6 +37,7 @@ const debug = require("debug")("eslint:cli");
/** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
/** @typedef {import("./eslint/eslint").LintResult} LintResult */
/** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
/** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
//------------------------------------------------------------------------------
// Helpers
@ -54,17 +59,20 @@ function quietFixPredicate(message) {
}
/**
* Translates the CLI options into the options expected by the CLIEngine.
* Translates the CLI options into the options expected by the ESLint constructor.
* @param {ParsedCLIOptions} cliOptions The CLI options to translate.
* @returns {ESLintOptions} The options object for the CLIEngine.
* @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the
* config to generate.
* @returns {Promise<ESLintOptions>} The options object for the ESLint constructor.
* @private
*/
function translateOptions({
async function translateOptions({
cache,
cacheFile,
cacheLocation,
cacheStrategy,
config,
configLookup,
env,
errorOnUnmatchedPattern,
eslintrc,
@ -85,19 +93,60 @@ function translateOptions({
resolvePluginsRelativeTo,
rule,
rulesdir
}) {
return {
allowInlineConfig: inlineConfig,
cache,
cacheLocation: cacheLocation || cacheFile,
cacheStrategy,
errorOnUnmatchedPattern,
extensions: ext,
fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
fixTypes: fixType,
ignore,
ignorePath,
overrideConfig: {
}, configType) {
let overrideConfig, overrideConfigFile;
const importer = new ModuleImporter();
if (configType === "flat") {
overrideConfigFile = (typeof config === "string") ? config : !configLookup;
if (overrideConfigFile === false) {
overrideConfigFile = void 0;
}
let globals = {};
if (global) {
globals = global.reduce((obj, name) => {
if (name.endsWith(":true")) {
obj[name.slice(0, -5)] = "writable";
} else {
obj[name] = "readonly";
}
return obj;
}, globals);
}
overrideConfig = [{
languageOptions: {
globals,
parserOptions: parserOptions || {}
},
rules: rule ? rule : {}
}];
if (parser) {
overrideConfig[0].languageOptions.parser = await importer.import(parser);
}
if (plugin) {
const plugins = {};
for (const pluginName of plugin) {
const shortName = naming.getShorthandName(pluginName, "eslint-plugin");
const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
plugins[shortName] = await importer.import(longName);
}
overrideConfig[0].plugins = plugins;
}
} else {
overrideConfigFile = config;
overrideConfig = {
env: env && env.reduce((obj, name) => {
obj[name] = true;
return obj;
@ -115,19 +164,40 @@ function translateOptions({
parserOptions,
plugins: plugin,
rules: rule
},
overrideConfigFile: config,
reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0,
resolvePluginsRelativeTo,
rulePaths: rulesdir,
useEslintrc: eslintrc
};
}
const options = {
allowInlineConfig: inlineConfig,
cache,
cacheLocation: cacheLocation || cacheFile,
cacheStrategy,
errorOnUnmatchedPattern,
fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
fixTypes: fixType,
ignore,
overrideConfig,
overrideConfigFile,
reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0
};
if (configType === "flat") {
options.ignorePatterns = ignorePattern;
} else {
options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
options.rulePaths = rulesdir;
options.useEslintrc = eslintrc;
options.extensions = ext;
options.ignorePath = ignorePath;
}
return options;
}
/**
* Count error messages.
* @param {LintResult[]} results The lint results.
* @returns {{errorCount:number;warningCount:number}} The number of error messages.
* @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages.
*/
function countErrors(results) {
let errorCount = 0;
@ -165,10 +235,11 @@ async function isDirectory(filePath) {
* @param {LintResult[]} results The results to print.
* @param {string} format The name of the formatter to use or the path to the formatter.
* @param {string} outputFile The path for the output file.
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
* @returns {Promise<boolean>} True if the printing succeeds, false if not.
* @private
*/
async function printResults(engine, results, format, outputFile) {
async function printResults(engine, results, format, outputFile, resultsMeta) {
let formatter;
try {
@ -178,7 +249,7 @@ async function printResults(engine, results, format, outputFile) {
return false;
}
const output = formatter.format(results);
const output = await formatter.format(results, resultsMeta);
if (output) {
if (outputFile) {
@ -204,6 +275,31 @@ async function printResults(engine, results, format, outputFile) {
return true;
}
/**
* Returns whether flat config should be used.
* @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
* @returns {Promise<boolean>} Where flat config should be used.
*/
async function shouldUseFlatConfig(allowFlatConfig) {
if (!allowFlatConfig) {
return false;
}
switch (process.env.ESLINT_USE_FLAT_CONFIG) {
case "true":
return true;
case "false":
return false;
default:
/*
* If neither explicitly enabled nor disabled, then use the presence
* of a flat config file to determine enablement.
*/
return !!(await findFlatConfigFile(process.cwd()));
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -218,19 +314,34 @@ const cli = {
* Executes the CLI based on an array of arguments that is passed in.
* @param {string|Array|Object} args The arguments to process.
* @param {string} [text] The text to lint (used for TTY).
* @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
* @returns {Promise<number>} The exit code for the operation.
*/
async execute(args, text) {
async execute(args, text, allowFlatConfig) {
if (Array.isArray(args)) {
debug("CLI args: %o", args.slice(2));
}
/*
* Before doing anything, we need to see if we are using a
* flat config file. If so, then we need to change the way command
* line args are parsed. This is temporary, and when we fully
* switch to flat config we can remove this logic.
*/
const usingFlatConfig = await shouldUseFlatConfig(allowFlatConfig);
debug("Using flat config?", usingFlatConfig);
const CLIOptions = createCLIOptions(usingFlatConfig);
/** @type {ParsedCLIOptions} */
let options;
try {
options = CLIOptions.parse(args);
} catch (error) {
debug("Error parsing CLI options:", error.message);
log.error(error.message);
return 2;
}
@ -251,6 +362,7 @@ const cli = {
log.info(RuntimeInfo.environment());
return 0;
} catch (err) {
debug("Error retrieving environment info");
log.error(err.message);
return 2;
}
@ -266,7 +378,9 @@ const cli = {
return 2;
}
const engine = new ESLint(translateOptions(options));
const engine = usingFlatConfig
? new FlatESLint(await translateOptions(options, "flat"))
: new ESLint(await translateOptions(options));
const fileConfig =
await engine.calculateConfigForFile(options.printConfig);
@ -289,7 +403,9 @@ const cli = {
return 2;
}
const engine = new ESLint(translateOptions(options));
const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint;
const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc"));
let results;
if (useStdin) {
@ -303,27 +419,34 @@ const cli = {
if (options.fix) {
debug("Fix mode enabled - applying fixes");
await ESLint.outputFixes(results);
await ActiveESLint.outputFixes(results);
}
let resultsToPrint = results;
if (options.quiet) {
debug("Quiet mode enabled - filtering out warnings");
resultsToPrint = ESLint.getErrorResults(resultsToPrint);
resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint);
}
if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
const resultCounts = countErrors(results);
const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings;
const resultsMeta = tooManyWarnings
? {
maxWarningsExceeded: {
maxWarnings: options.maxWarnings,
foundWarnings: resultCounts.warningCount
}
}
: {};
if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) {
// Errors and warnings from the original unfiltered results should determine the exit code
const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
const tooManyWarnings =
options.maxWarnings >= 0 && warningCount > options.maxWarnings;
const shouldExitForFatalErrors =
options.exitOnFatalError && fatalErrorCount > 0;
options.exitOnFatalError && resultCounts.fatalErrorCount > 0;
if (!errorCount && tooManyWarnings) {
if (!resultCounts.errorCount && tooManyWarnings) {
log.error(
"ESLint found too many warnings (maximum: %s).",
options.maxWarnings
@ -334,7 +457,7 @@ const cli = {
return 2;
}
return (errorCount || tooManyWarnings) ? 1 : 0;
return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0;
}
return 2;

View file

@ -15,7 +15,6 @@ const Rules = require("../rules");
// Helpers
//-----------------------------------------------------------------------------
exports.defaultConfig = [
{
plugins: {
@ -26,7 +25,7 @@ exports.defaultConfig = [
/*
* Because we try to delay loading rules until absolutely
* necessary, a proxy allows us to hook into the lazy-loading
* necessary, a proxy allows us to hook into the lazy-loading
* aspect of the rules map while still keeping all of the
* relevant configuration inside of the config array.
*/
@ -41,12 +40,31 @@ exports.defaultConfig = [
})
}
},
ignores: [
"**/node_modules/**",
".git/**"
],
languageOptions: {
parser: "@/espree"
sourceType: "module",
ecmaVersion: "latest",
parser: "@/espree",
parserOptions: {}
}
},
// default ignores are listed here
{
ignores: [
"**/node_modules/*",
".git/"
]
},
// intentionally empty config to ensure these files are globbed by default
{
files: ["**/*.js", "**/*.mjs"]
},
{
files: ["**/*.cjs"],
languageOptions: {
sourceType: "commonjs",
ecmaVersion: "latest"
}
}
];

View file

@ -14,7 +14,6 @@ const { flatConfigSchema } = require("./flat-config-schema");
const { RuleValidator } = require("./rule-validator");
const { defaultConfig } = require("./default-config");
const recommendedConfig = require("../../conf/eslint-recommended");
const allConfig = require("../../conf/eslint-all");
//-----------------------------------------------------------------------------
// Helpers
@ -37,6 +36,8 @@ function splitPluginIdentifier(identifier) {
};
}
const originalBaseConfig = Symbol("originalBaseConfig");
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
@ -49,19 +50,43 @@ class FlatConfigArray extends ConfigArray {
/**
* Creates a new instance.
* @param {*[]} configs An array of configuration information.
* @param {{basePath: string, baseConfig: FlatConfig}} options The options
* @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
* to use for the config array instance.
*/
constructor(configs, { basePath, baseConfig = defaultConfig }) {
constructor(configs, {
basePath,
shouldIgnore = true,
baseConfig = defaultConfig
} = {}) {
super(configs, {
basePath,
schema: flatConfigSchema
});
this.unshift(baseConfig);
if (baseConfig[Symbol.iterator]) {
this.unshift(...baseConfig);
} else {
this.unshift(baseConfig);
}
/**
* The base config used to build the config array.
* @type {Array<FlatConfig>}
*/
this[originalBaseConfig] = baseConfig;
Object.defineProperty(this, originalBaseConfig, { writable: false });
/**
* Determines if `ignores` fields should be honored.
* If true, then all `ignores` fields are honored.
* if false, then only `ignores` fields in the baseConfig are honored.
* @type {boolean}
*/
this.shouldIgnore = shouldIgnore;
Object.defineProperty(this, "shouldIgnore", { writable: false });
}
/* eslint-disable class-methods-use-this */
/* eslint-disable class-methods-use-this -- Desired as instance method */
/**
* Replaces a config with another config to allow us to put strings
* in the config array that will be replaced by objects before
@ -75,7 +100,30 @@ class FlatConfigArray extends ConfigArray {
}
if (config === "eslint:all") {
return allConfig;
/*
* Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules
* when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones,
* so requiring `eslint-all.js` module loads all rule modules as a consequence.
*/
return require("../../conf/eslint-all");
}
/*
* If `shouldIgnore` is false, we remove any ignore patterns specified
* in the config so long as it's not a default config and it doesn't
* have a `files` entry.
*/
if (
!this.shouldIgnore &&
!this[originalBaseConfig].includes(config) &&
config.ignores &&
!config.files
) {
/* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
const { ignores, ...otherKeys } = config;
return otherKeys;
}
return config;
@ -91,34 +139,75 @@ class FlatConfigArray extends ConfigArray {
[ConfigArraySymbol.finalizeConfig](config) {
const { plugins, languageOptions, processor } = config;
let parserName, processorName;
let invalidParser = false,
invalidProcessor = false;
// Check parser value
if (languageOptions && languageOptions.parser && typeof languageOptions.parser === "string") {
const { pluginName, objectName: parserName } = splitPluginIdentifier(languageOptions.parser);
if (languageOptions && languageOptions.parser) {
if (typeof languageOptions.parser === "string") {
const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser);
if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[parserName]) {
throw new TypeError(`Key "parser": Could not find "${parserName}" in plugin "${pluginName}".`);
parserName = languageOptions.parser;
if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) {
throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`);
}
languageOptions.parser = plugins[pluginName].parsers[localParserName];
} else {
invalidParser = true;
}
languageOptions.parser = plugins[pluginName].parsers[parserName];
}
// Check processor value
if (processor && typeof processor === "string") {
const { pluginName, objectName: processorName } = splitPluginIdentifier(processor);
if (processor) {
if (typeof processor === "string") {
const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[processorName]) {
throw new TypeError(`Key "processor": Could not find "${processorName}" in plugin "${pluginName}".`);
processorName = processor;
if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
}
config.processor = plugins[pluginName].processors[localProcessorName];
} else {
invalidProcessor = true;
}
config.processor = plugins[pluginName].processors[processorName];
}
ruleValidator.validate(config);
// apply special logic for serialization into JSON
/* eslint-disable object-shorthand -- shorthand would change "this" value */
Object.defineProperty(config, "toJSON", {
value: function() {
if (invalidParser) {
throw new Error("Caching is not supported when parser is an object.");
}
if (invalidProcessor) {
throw new Error("Caching is not supported when processor is an object.");
}
return {
...this,
plugins: Object.keys(plugins),
languageOptions: {
...languageOptions,
parser: parserName
},
processor: processorName
};
}
});
/* eslint-enable object-shorthand -- ok to enable now */
return config;
}
/* eslint-enable class-methods-use-this */
/* eslint-enable class-methods-use-this -- Desired as instance method */
}

111
node_modules/eslint/lib/config/flat-config-helpers.js generated vendored Normal file
View file

@ -0,0 +1,111 @@
/**
* @fileoverview Shared functions to work with configs.
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Functions
//-----------------------------------------------------------------------------
/**
* Parses a ruleId into its plugin and rule parts.
* @param {string} ruleId The rule ID to parse.
* @returns {{pluginName:string,ruleName:string}} The plugin and rule
* parts of the ruleId;
*/
function parseRuleId(ruleId) {
let pluginName, ruleName;
// distinguish between core rules and plugin rules
if (ruleId.includes("/")) {
// mimic scoped npm packages
if (ruleId.startsWith("@")) {
pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
} else {
pluginName = ruleId.slice(0, ruleId.indexOf("/"));
}
ruleName = ruleId.slice(pluginName.length + 1);
} else {
pluginName = "@";
ruleName = ruleId;
}
return {
pluginName,
ruleName
};
}
/**
* Retrieves a rule instance from a given config based on the ruleId.
* @param {string} ruleId The rule ID to look for.
* @param {FlatConfig} config The config to search.
* @returns {import("../shared/types").Rule|undefined} The rule if found
* or undefined if not.
*/
function getRuleFromConfig(ruleId, config) {
const { pluginName, ruleName } = parseRuleId(ruleId);
const plugin = config.plugins && config.plugins[pluginName];
let rule = plugin && plugin.rules && plugin.rules[ruleName];
// normalize function rules into objects
if (rule && typeof rule === "function") {
rule = {
create: rule
};
}
return rule;
}
/**
* Gets a complete options schema for a rule.
* @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
* @returns {Object} JSON Schema for the rule's options.
*/
function getRuleOptionsSchema(rule) {
if (!rule) {
return null;
}
const schema = rule.schema || rule.meta && rule.meta.schema;
if (Array.isArray(schema)) {
if (schema.length) {
return {
type: "array",
items: schema,
minItems: 0,
maxItems: schema.length
};
}
return {
type: "array",
minItems: 0,
maxItems: 0
};
}
// Given a full schema, leave it alone
return schema || null;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
module.exports = {
parseRuleId,
getRuleFromConfig,
getRuleOptionsSchema
};

View file

@ -195,13 +195,6 @@ function assertIsObjectOrString(value) {
// Low-Level Schemas
//-----------------------------------------------------------------------------
/** @type {ObjectPropertySchema} */
const numberSchema = {
merge: "replace",
validate: "number"
};
/** @type {ObjectPropertySchema} */
const booleanSchema = {
merge: "replace",
@ -336,7 +329,7 @@ const rulesSchema = {
// avoid hairy edge case
if (ruleId === "__proto__") {
/* eslint-disable-next-line no-proto */
/* eslint-disable-next-line no-proto -- Though deprecated, may still be present */
delete result.__proto__;
continue;
}
@ -415,6 +408,18 @@ const rulesSchema = {
}
};
/** @type {ObjectPropertySchema} */
const ecmaVersionSchema = {
merge: "replace",
validate(value) {
if (typeof value === "number" || value === "latest") {
return;
}
throw new TypeError("Expected a number or \"latest\".");
}
};
/** @type {ObjectPropertySchema} */
const sourceTypeSchema = {
merge: "replace",
@ -439,7 +444,7 @@ exports.flatConfigSchema = {
},
languageOptions: {
schema: {
ecmaVersion: numberSchema,
ecmaVersion: ecmaVersionSchema,
sourceType: sourceTypeSchema,
globals: globalsSchema,
parser: parserSchema,

View file

@ -10,74 +10,59 @@
//-----------------------------------------------------------------------------
const ajv = require("../shared/ajv")();
const {
parseRuleId,
getRuleFromConfig,
getRuleOptionsSchema
} = require("./flat-config-helpers");
const ruleReplacements = require("../../conf/replacements.json");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Finds a rule with the given ID in the given config.
* @param {string} ruleId The ID of the rule to find.
* Throws a helpful error when a rule cannot be found.
* @param {Object} ruleId The rule identifier.
* @param {string} ruleId.pluginName The ID of the rule to find.
* @param {string} ruleId.ruleName The ID of the rule to find.
* @param {Object} config The config to search in.
* @returns {{create: Function, schema: (Array|null)}} THe rule object.
* @throws {TypeError} For missing plugin or rule.
* @returns {void}
*/
function findRuleDefinition(ruleId, config) {
const ruleIdParts = ruleId.split("/");
let pluginName, ruleName;
function throwRuleNotFoundError({ pluginName, ruleName }, config) {
// built-in rule
if (ruleIdParts.length === 1) {
pluginName = "@";
ruleName = ruleIdParts[0];
} else {
ruleName = ruleIdParts.pop();
pluginName = ruleIdParts.join("/");
}
const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`;
if (!config.plugins || !config.plugins[pluginName]) {
throw new TypeError(`Key "rules": Key "${ruleId}": Could not find plugin "${pluginName}".`);
}
const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
if (!config.plugins[pluginName].rules || !config.plugins[pluginName].rules[ruleName]) {
throw new TypeError(`Key "rules": Key "${ruleId}": Could not find "${ruleName}" in plugin "${pluginName}".`);
}
// if the plugin exists then we need to check if the rule exists
if (config.plugins && config.plugins[pluginName]) {
const replacementRuleName = ruleReplacements.rules[ruleName];
return config.plugins[pluginName].rules[ruleName];
if (pluginName === "@" && replacementRuleName) {
}
errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`;
/**
* Gets a complete options schema for a rule.
* @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
* @returns {Object} JSON Schema for the rule's options.
*/
function getRuleOptionsSchema(rule) {
} else {
if (!rule) {
return null;
}
errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
const schema = rule.schema || rule.meta && rule.meta.schema;
// otherwise, let's see if we can find the rule name elsewhere
for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) {
if (otherPlugin.rules && otherPlugin.rules[ruleName]) {
errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`;
break;
}
}
if (Array.isArray(schema)) {
if (schema.length) {
return {
type: "array",
items: schema,
minItems: 0,
maxItems: schema.length
};
}
return {
type: "array",
minItems: 0,
maxItems: 0
};
// falls through to throw error
}
// Given a full schema, leave it alone
return schema || null;
throw new TypeError(errorMessage);
}
//-----------------------------------------------------------------------------
@ -98,7 +83,6 @@ class RuleValidator {
* A collection of compiled validators for rules that have already
* been validated.
* @type {WeakMap}
* @property validators
*/
this.validators = new WeakMap();
}
@ -137,7 +121,11 @@ class RuleValidator {
continue;
}
const rule = findRuleDefinition(ruleId, config);
const rule = getRuleFromConfig(ruleId, config);
if (!rule) {
throwRuleNotFoundError(parseRuleId(ruleId), config);
}
// Precompile and cache validator the first time
if (!this.validators.has(rule)) {

940
node_modules/eslint/lib/eslint/eslint-helpers.js generated vendored Normal file
View file

@ -0,0 +1,940 @@
/**
* @fileoverview Helper functions for ESLint class
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const path = require("path");
const fs = require("fs");
const fsp = fs.promises;
const isGlob = require("is-glob");
const hash = require("../cli-engine/hash");
const minimatch = require("minimatch");
const util = require("util");
const fswalk = require("@nodelib/fs.walk");
const globParent = require("glob-parent");
const isPathInside = require("is-path-inside");
//-----------------------------------------------------------------------------
// Fixup references
//-----------------------------------------------------------------------------
const doFsWalk = util.promisify(fswalk.walk);
const Minimatch = minimatch.Minimatch;
const MINIMATCH_OPTIONS = { dot: true };
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/**
* @typedef {Object} GlobSearch
* @property {Array<string>} patterns The normalized patterns to use for a search.
* @property {Array<string>} rawPatterns The patterns as entered by the user
* before doing any normalization.
*/
//-----------------------------------------------------------------------------
// Errors
//-----------------------------------------------------------------------------
/**
* The error type when no files match a glob.
*/
class NoFilesFoundError extends Error {
/**
* @param {string} pattern The glob pattern which was not found.
* @param {boolean} globEnabled If `false` then the pattern was a glob pattern, but glob was disabled.
*/
constructor(pattern, globEnabled) {
super(`No files matching '${pattern}' were found${!globEnabled ? " (glob was disabled)" : ""}.`);
this.messageTemplate = "file-not-found";
this.messageData = { pattern, globDisabled: !globEnabled };
}
}
/**
* The error type when a search fails to match multiple patterns.
*/
class UnmatchedSearchPatternsError extends Error {
/**
* @param {Object} options The options for the error.
* @param {string} options.basePath The directory that was searched.
* @param {Array<string>} options.unmatchedPatterns The glob patterns
* which were not found.
* @param {Array<string>} options.patterns The glob patterns that were
* searched.
* @param {Array<string>} options.rawPatterns The raw glob patterns that
* were searched.
*/
constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
this.basePath = basePath;
this.unmatchedPatterns = unmatchedPatterns;
this.patterns = patterns;
this.rawPatterns = rawPatterns;
}
}
/**
* The error type when there are files matched by a glob, but all of them have been ignored.
*/
class AllFilesIgnoredError extends Error {
/**
* @param {string} pattern The glob pattern which was not found.
*/
constructor(pattern) {
super(`All files matched by '${pattern}' are ignored.`);
this.messageTemplate = "all-files-ignored";
this.messageData = { pattern };
}
}
//-----------------------------------------------------------------------------
// General Helpers
//-----------------------------------------------------------------------------
/**
* Check if a given value is a non-empty string or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if `x` is a non-empty string.
*/
function isNonEmptyString(x) {
return typeof x === "string" && x.trim() !== "";
}
/**
* Check if a given value is an array of non-empty strings or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if `x` is an array of non-empty strings.
*/
function isArrayOfNonEmptyString(x) {
return Array.isArray(x) && x.every(isNonEmptyString);
}
//-----------------------------------------------------------------------------
// File-related Helpers
//-----------------------------------------------------------------------------
/**
* Normalizes slashes in a file pattern to posix-style.
* @param {string} pattern The pattern to replace slashes in.
* @returns {string} The pattern with slashes normalized.
*/
function normalizeToPosix(pattern) {
return pattern.replace(/\\/gu, "/");
}
/**
* Check if a string is a glob pattern or not.
* @param {string} pattern A glob pattern.
* @returns {boolean} `true` if the string is a glob pattern.
*/
function isGlobPattern(pattern) {
return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
}
/**
* Determines if a given glob pattern will return any results.
* Used primarily to help with useful error messages.
* @param {Object} options The options for the function.
* @param {string} options.basePath The directory to search.
* @param {string} options.pattern A glob pattern to match.
* @returns {Promise<boolean>} True if there is a glob match, false if not.
*/
function globMatch({ basePath, pattern }) {
let found = false;
const patternToUse = path.isAbsolute(pattern)
? normalizeToPosix(path.relative(basePath, pattern))
: pattern;
const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
const fsWalkSettings = {
deepFilter(entry) {
const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
return !found && matcher.match(relativePath, true);
},
entryFilter(entry) {
if (found || entry.dirent.isDirectory()) {
return false;
}
const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
if (matcher.match(relativePath)) {
found = true;
return true;
}
return false;
}
};
return new Promise(resolve => {
// using a stream so we can exit early because we just need one match
const globStream = fswalk.walkStream(basePath, fsWalkSettings);
globStream.on("data", () => {
globStream.destroy();
resolve(true);
});
// swallow errors as they're not important here
globStream.on("error", () => { });
globStream.on("end", () => {
resolve(false);
});
globStream.read();
});
}
/**
* Searches a directory looking for matching glob patterns. This uses
* the config array's logic to determine if a directory or file should
* be ignored, so it is consistent with how ignoring works throughout
* ESLint.
* @param {Object} options The options for this function.
* @param {string} options.basePath The directory to search.
* @param {Array<string>} options.patterns An array of glob patterns
* to match.
* @param {Array<string>} options.rawPatterns An array of glob patterns
* as the user inputted them. Used for errors.
* @param {FlatConfigArray} options.configs The config array to use for
* determining what to ignore.
* @param {boolean} options.errorOnUnmatchedPattern Determines if an error
* should be thrown when a pattern is unmatched.
* @returns {Promise<Array<string>>} An array of matching file paths
* or an empty array if there are no matches.
* @throws {UnmatchedSearchPatternsErrror} If there is a pattern that doesn't
* match any files.
*/
async function globSearch({
basePath,
patterns,
rawPatterns,
configs,
errorOnUnmatchedPattern
}) {
if (patterns.length === 0) {
return [];
}
/*
* In this section we are converting the patterns into Minimatch
* instances for performance reasons. Because we are doing the same
* matches repeatedly, it's best to compile those patterns once and
* reuse them multiple times.
*
* To do that, we convert any patterns with an absolute path into a
* relative path and normalize it to Posix-style slashes. We also keep
* track of the relative patterns to map them back to the original
* patterns, which we need in order to throw an error if there are any
* unmatched patterns.
*/
const relativeToPatterns = new Map();
const matchers = patterns.map((pattern, i) => {
const patternToUse = path.isAbsolute(pattern)
? normalizeToPosix(path.relative(basePath, pattern))
: pattern;
relativeToPatterns.set(patternToUse, patterns[i]);
return new Minimatch(patternToUse, MINIMATCH_OPTIONS);
});
/*
* We track unmatched patterns because we may want to throw an error when
* they occur. To start, this set is initialized with all of the patterns.
* Every time a match occurs, the pattern is removed from the set, making
* it easy to tell if we have any unmatched patterns left at the end of
* search.
*/
const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
const filePaths = (await doFsWalk(basePath, {
deepFilter(entry) {
const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
return matchesPattern && !configs.isDirectoryIgnored(entry.path);
},
entryFilter(entry) {
const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
// entries may be directories or files so filter out directories
if (entry.dirent.isDirectory()) {
return false;
}
/*
* Optimization: We need to track when patterns are left unmatched
* and so we use `unmatchedPatterns` to do that. There is a bit of
* complexity here because the same file can be matched by more than
* one pattern. So, when we start, we actually need to test every
* pattern against every file. Once we know there are no remaining
* unmatched patterns, then we can switch to just looking for the
* first matching pattern for improved speed.
*/
const matchesPattern = unmatchedPatterns.size > 0
? matchers.reduce((previousValue, matcher) => {
const pathMatches = matcher.match(relativePath);
/*
* We updated the unmatched patterns set only if the path
* matches and the file isn't ignored. If the file is
* ignored, that means there wasn't a match for the
* pattern so it should not be removed.
*
* Performance note: isFileIgnored() aggressively caches
* results so there is no performance penalty for calling
* it twice with the same argument.
*/
if (pathMatches && !configs.isFileIgnored(entry.path)) {
unmatchedPatterns.delete(matcher.pattern);
}
return pathMatches || previousValue;
}, false)
: matchers.some(matcher => matcher.match(relativePath));
return matchesPattern && !configs.isFileIgnored(entry.path);
}
})).map(entry => entry.path);
// now check to see if we have any unmatched patterns
if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
throw new UnmatchedSearchPatternsError({
basePath,
unmatchedPatterns: [...unmatchedPatterns].map(
pattern => relativeToPatterns.get(pattern)
),
patterns,
rawPatterns
});
}
return filePaths;
}
/**
* Throws an error for unmatched patterns. The error will only contain information about the first one.
* Checks to see if there are any ignored results for a given search.
* @param {Object} options The options for this function.
* @param {string} options.basePath The directory to search.
* @param {Array<string>} options.patterns An array of glob patterns
* that were used in the original search.
* @param {Array<string>} options.rawPatterns An array of glob patterns
* as the user inputted them. Used for errors.
* @param {Array<string>} options.unmatchedPatterns A non-empty array of glob patterns
* that were unmatched in the original search.
* @returns {void} Always throws an error.
* @throws {NoFilesFoundError} If the first unmatched pattern
* doesn't match any files even when there are no ignores.
* @throws {AllFilesIgnoredError} If the first unmatched pattern
* matches some files when there are no ignores.
*/
async function throwErrorForUnmatchedPatterns({
basePath,
patterns,
rawPatterns,
unmatchedPatterns
}) {
const pattern = unmatchedPatterns[0];
const rawPattern = rawPatterns[patterns.indexOf(pattern)];
const patternHasMatch = await globMatch({
basePath,
pattern
});
if (patternHasMatch) {
throw new AllFilesIgnoredError(rawPattern);
}
// if we get here there are truly no matches
throw new NoFilesFoundError(rawPattern, true);
}
/**
* Performs multiple glob searches in parallel.
* @param {Object} options The options for this function.
* @param {Map<string,GlobSearch>} options.searches
* An array of glob patterns to match.
* @param {FlatConfigArray} options.configs The config array to use for
* determining what to ignore.
* @param {boolean} options.errorOnUnmatchedPattern Determines if an
* unmatched glob pattern should throw an error.
* @returns {Promise<Array<string>>} An array of matching file paths
* or an empty array if there are no matches.
*/
async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
/*
* For convenience, we normalized the search map into an array of objects.
* Next, we filter out all searches that have no patterns. This happens
* primarily for the cwd, which is prepopulated in the searches map as an
* optimization. However, if it has no patterns, it means all patterns
* occur outside of the cwd and we can safely filter out that search.
*/
const normalizedSearches = [...searches].map(
([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns })
).filter(({ patterns }) => patterns.length > 0);
const results = await Promise.allSettled(
normalizedSearches.map(
({ basePath, patterns, rawPatterns }) => globSearch({
basePath,
patterns,
rawPatterns,
configs,
errorOnUnmatchedPattern
})
)
);
const filePaths = [];
for (let i = 0; i < results.length; i++) {
const result = results[i];
const currentSearch = normalizedSearches[i];
if (result.status === "fulfilled") {
// if the search was successful just add the results
if (result.value.length > 0) {
filePaths.push(...result.value);
}
continue;
}
// if we make it here then there was an error
const error = result.reason;
// unexpected errors should be re-thrown
if (!error.basePath) {
throw error;
}
if (errorOnUnmatchedPattern) {
await throwErrorForUnmatchedPatterns({
...currentSearch,
unmatchedPatterns: error.unmatchedPatterns
});
}
}
return [...new Set(filePaths)];
}
/**
* Finds all files matching the options specified.
* @param {Object} args The arguments objects.
* @param {Array<string>} args.patterns An array of glob patterns.
* @param {boolean} args.globInputPaths true to interpret glob patterns,
* false to not interpret glob patterns.
* @param {string} args.cwd The current working directory to find from.
* @param {FlatConfigArray} args.configs The configs for the current run.
* @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
* should throw an error.
* @returns {Promise<Array<string>>} The fully resolved file paths.
* @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern.
* @throws {NoFilesFoundError} If no files matched the given patterns.
*/
async function findFiles({
patterns,
globInputPaths,
cwd,
configs,
errorOnUnmatchedPattern
}) {
const results = [];
const missingPatterns = [];
let globbyPatterns = [];
let rawPatterns = [];
const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
// check to see if we have explicit files and directories
const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
const stats = await Promise.all(
filePaths.map(
filePath => fsp.stat(filePath).catch(() => { })
)
);
stats.forEach((stat, index) => {
const filePath = filePaths[index];
const pattern = normalizeToPosix(patterns[index]);
if (stat) {
// files are added directly to the list
if (stat.isFile()) {
results.push({
filePath,
ignored: configs.isFileIgnored(filePath)
});
}
// directories need extensions attached
if (stat.isDirectory()) {
// group everything in cwd together and split out others
if (isPathInside(filePath, cwd)) {
({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
} else {
if (!searches.has(filePath)) {
searches.set(filePath, { patterns: [], rawPatterns: [] });
}
({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
}
globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
rawPatterns.push(pattern);
}
return;
}
// save patterns for later use based on whether globs are enabled
if (globInputPaths && isGlobPattern(filePath)) {
const basePath = globParent(filePath);
// group in cwd if possible and split out others
if (isPathInside(basePath, cwd)) {
({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
} else {
if (!searches.has(basePath)) {
searches.set(basePath, { patterns: [], rawPatterns: [] });
}
({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
}
globbyPatterns.push(filePath);
rawPatterns.push(pattern);
} else {
missingPatterns.push(pattern);
}
});
// there were patterns that didn't match anything, tell the user
if (errorOnUnmatchedPattern && missingPatterns.length) {
throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
}
// now we are safe to do the search
const globbyResults = await globMultiSearch({
searches,
configs,
errorOnUnmatchedPattern
});
return [
...results,
...globbyResults.map(filePath => ({
filePath: path.resolve(filePath),
ignored: false
}))
];
}
/**
* Checks whether a file exists at the given location
* @param {string} resolvedPath A path from the CWD
* @throws {Error} As thrown by `fs.statSync` or `fs.isFile`.
* @returns {boolean} `true` if a file exists
*/
function fileExists(resolvedPath) {
try {
return fs.statSync(resolvedPath).isFile();
} catch (error) {
if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
return false;
}
throw error;
}
}
/**
* Checks whether a directory exists at the given location
* @param {string} resolvedPath A path from the CWD
* @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`.
* @returns {boolean} `true` if a directory exists
*/
function directoryExists(resolvedPath) {
try {
return fs.statSync(resolvedPath).isDirectory();
} catch (error) {
if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
return false;
}
throw error;
}
}
//-----------------------------------------------------------------------------
// Results-related Helpers
//-----------------------------------------------------------------------------
/**
* Checks if the given message is an error message.
* @param {LintMessage} message The message to check.
* @returns {boolean} Whether or not the message is an error message.
* @private
*/
function isErrorMessage(message) {
return message.severity === 2;
}
/**
* Returns result with warning by ignore settings
* @param {string} filePath File path of checked code
* @param {string} baseDir Absolute path of base directory
* @returns {LintResult} Result with single warning
* @private
*/
function createIgnoreResult(filePath, baseDir) {
let message;
const isHidden = filePath.split(path.sep)
.find(segment => /^\./u.test(segment));
const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
if (isHidden) {
message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
} else if (isInNodeModules) {
message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
} else {
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
}
return {
filePath: path.resolve(filePath),
messages: [
{
fatal: false,
severity: 1,
message
}
],
suppressedMessages: [],
errorCount: 0,
warningCount: 1,
fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
};
}
//-----------------------------------------------------------------------------
// Options-related Helpers
//-----------------------------------------------------------------------------
/**
* Check if a given value is a valid fix type or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if `x` is valid fix type.
*/
function isFixType(x) {
return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
}
/**
* Check if a given value is an array of fix types or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if `x` is an array of fix types.
*/
function isFixTypeArray(x) {
return Array.isArray(x) && x.every(isFixType);
}
/**
* The error for invalid options.
*/
class ESLintInvalidOptionsError extends Error {
constructor(messages) {
super(`Invalid Options:\n- ${messages.join("\n- ")}`);
this.code = "ESLINT_INVALID_OPTIONS";
Error.captureStackTrace(this, ESLintInvalidOptionsError);
}
}
/**
* Validates and normalizes options for the wrapped CLIEngine instance.
* @param {FlatESLintOptions} options The options to process.
* @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
* @returns {FlatESLintOptions} The normalized options.
*/
function processOptions({
allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
baseConfig = null,
cache = false,
cacheLocation = ".eslintcache",
cacheStrategy = "metadata",
cwd = process.cwd(),
errorOnUnmatchedPattern = true,
fix = false,
fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
globInputPaths = true,
ignore = true,
ignorePatterns = null,
overrideConfig = null,
overrideConfigFile = null,
plugins = {},
reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that.
...unknownOptions
}) {
const errors = [];
const unknownOptionKeys = Object.keys(unknownOptions);
if (unknownOptionKeys.length >= 1) {
errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
if (unknownOptionKeys.includes("cacheFile")) {
errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
}
if (unknownOptionKeys.includes("configFile")) {
errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
}
if (unknownOptionKeys.includes("envs")) {
errors.push("'envs' has been removed.");
}
if (unknownOptionKeys.includes("extensions")) {
errors.push("'extensions' has been removed.");
}
if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) {
errors.push("'resolvePluginsRelativeTo' has been removed.");
}
if (unknownOptionKeys.includes("globals")) {
errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
}
if (unknownOptionKeys.includes("ignorePath")) {
errors.push("'ignorePath' has been removed.");
}
if (unknownOptionKeys.includes("ignorePattern")) {
errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
}
if (unknownOptionKeys.includes("parser")) {
errors.push("'parser' has been removed. Please use the 'overrideConfig.languageOptions.parser' option instead.");
}
if (unknownOptionKeys.includes("parserOptions")) {
errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.languageOptions.parserOptions' option instead.");
}
if (unknownOptionKeys.includes("rules")) {
errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
}
if (unknownOptionKeys.includes("rulePaths")) {
errors.push("'rulePaths' has been removed. Please define your rules using plugins.");
}
}
if (typeof allowInlineConfig !== "boolean") {
errors.push("'allowInlineConfig' must be a boolean.");
}
if (typeof baseConfig !== "object") {
errors.push("'baseConfig' must be an object or null.");
}
if (typeof cache !== "boolean") {
errors.push("'cache' must be a boolean.");
}
if (!isNonEmptyString(cacheLocation)) {
errors.push("'cacheLocation' must be a non-empty string.");
}
if (
cacheStrategy !== "metadata" &&
cacheStrategy !== "content"
) {
errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
}
if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
errors.push("'cwd' must be an absolute path.");
}
if (typeof errorOnUnmatchedPattern !== "boolean") {
errors.push("'errorOnUnmatchedPattern' must be a boolean.");
}
if (typeof fix !== "boolean" && typeof fix !== "function") {
errors.push("'fix' must be a boolean or a function.");
}
if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
}
if (typeof globInputPaths !== "boolean") {
errors.push("'globInputPaths' must be a boolean.");
}
if (typeof ignore !== "boolean") {
errors.push("'ignore' must be a boolean.");
}
if (typeof overrideConfig !== "object") {
errors.push("'overrideConfig' must be an object or null.");
}
if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
}
if (typeof plugins !== "object") {
errors.push("'plugins' must be an object or null.");
} else if (plugins !== null && Object.keys(plugins).includes("")) {
errors.push("'plugins' must not include an empty string.");
}
if (Array.isArray(plugins)) {
errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
}
if (
reportUnusedDisableDirectives !== "error" &&
reportUnusedDisableDirectives !== "warn" &&
reportUnusedDisableDirectives !== "off" &&
reportUnusedDisableDirectives !== null
) {
errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
}
if (errors.length > 0) {
throw new ESLintInvalidOptionsError(errors);
}
return {
allowInlineConfig,
baseConfig,
cache,
cacheLocation,
cacheStrategy,
// when overrideConfigFile is true that means don't do config file lookup
configFile: overrideConfigFile === true ? false : overrideConfigFile,
overrideConfig,
cwd,
errorOnUnmatchedPattern,
fix,
fixTypes,
globInputPaths,
ignore,
ignorePatterns,
reportUnusedDisableDirectives
};
}
//-----------------------------------------------------------------------------
// Cache-related helpers
//-----------------------------------------------------------------------------
/**
* return the cacheFile to be used by eslint, based on whether the provided parameter is
* a directory or looks like a directory (ends in `path.sep`), in which case the file
* name will be the `cacheFile/.cache_hashOfCWD`
*
* if cacheFile points to a file or looks like a file then in will just use that file
* @param {string} cacheFile The name of file to be used to store the cache
* @param {string} cwd Current working directory
* @returns {string} the resolved path to the cache file
*/
function getCacheFile(cacheFile, cwd) {
/*
* make sure the path separators are normalized for the environment/os
* keeping the trailing path separator if present
*/
const normalizedCacheFile = path.normalize(cacheFile);
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
* @returns {string} the resolved path to the cacheFile
*/
function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
}
let fileStats;
try {
fileStats = fs.lstatSync(resolvedCacheFile);
} catch {
fileStats = null;
}
/*
* in case the file exists we need to verify if the provided path
* is a directory or a file. If it is a directory we want to create a file
* inside that directory
*/
if (fileStats) {
/*
* is a directory or is a file, but the original file the user provided
* looks like a directory but `path.resolve` removed the `last path.sep`
* so we need to still treat this like a directory
*/
if (fileStats.isDirectory() || looksLikeADirectory) {
return getCacheFileForDirectory();
}
// is file so just use that file
return resolvedCacheFile;
}
/*
* here we known the file or directory doesn't exist,
* so we will try to infer if its a directory if it looks like a directory
* for the current operating system.
*/
// if the last character passed is a path separator we assume is a directory
if (looksLikeADirectory) {
return getCacheFileForDirectory();
}
return resolvedCacheFile;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
module.exports = {
isGlobPattern,
directoryExists,
fileExists,
findFiles,
isNonEmptyString,
isArrayOfNonEmptyString,
createIgnoreResult,
isErrorMessage,
processOptions,
getCacheFile
};

View file

@ -32,9 +32,17 @@ const { version } = require("../../package.json");
/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
/** @typedef {import("../shared/types").ConfigData} ConfigData */
/** @typedef {import("../shared/types").LintMessage} LintMessage */
/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
/** @typedef {import("../shared/types").Plugin} Plugin */
/** @typedef {import("../shared/types").Rule} Rule */
/** @typedef {import("./load-formatter").Formatter} Formatter */
/** @typedef {import("../shared/types").LintResult} LintResult */
/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
/**
* The main formatter object.
* @typedef LoadedFormatter
* @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise<string>} format format function.
*/
/**
* The options with which to configure the ESLint instance.
@ -54,7 +62,7 @@ const { version } = require("../../package.json");
* @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
* @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
* @property {string} [overrideConfigFile] The configuration file to use.
* @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
* @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
* @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
* @property {string[]} [rulePaths] An array of directories to load custom rules from.
@ -68,20 +76,6 @@ const { version } = require("../../package.json");
* @property {Object} definition The plugin definition.
*/
/**
* A linting result.
* @typedef {Object} LintResult
* @property {string} filePath The path to the file that was linted.
* @property {LintMessage[]} messages All of the messages for the result.
* @property {number} errorCount Number of errors for the result.
* @property {number} warningCount Number of warnings for the result.
* @property {number} fixableErrorCount Number of fixable errors for the result.
* @property {number} fixableWarningCount Number of fixable warnings for the result.
* @property {string} [source] The source code of the file that was linted.
* @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
* @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
*/
/**
* Private members for the `ESLint` instance.
* @typedef {Object} ESLintPrivateMembers
@ -111,9 +105,9 @@ function isNonEmptyString(x) {
}
/**
* Check if a given value is an array of non-empty stringss or not.
* Check if a given value is an array of non-empty strings or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if `x` is an array of non-empty stringss.
* @returns {boolean} `true` if `x` is an array of non-empty strings.
*/
function isArrayOfNonEmptyString(x) {
return Array.isArray(x) && x.every(isNonEmptyString);
@ -125,7 +119,7 @@ function isArrayOfNonEmptyString(x) {
* @returns {boolean} `true` if `x` is valid fix type.
*/
function isFixType(x) {
return x === "problem" || x === "suggestion" || x === "layout";
return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
}
/**
@ -151,6 +145,7 @@ class ESLintInvalidOptionsError extends Error {
/**
* Validates and normalizes options for the wrapped CLIEngine instance.
* @param {ESLintOptions} options The options to process.
* @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
* @returns {ESLintOptions} The normalized options.
*/
function processOptions({
@ -237,7 +232,7 @@ function processOptions({
errors.push("'fix' must be a boolean or a function.");
}
if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
errors.push("'fixTypes' must be an array of any of \"problem\", \"suggestion\", and \"layout\".");
errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
}
if (typeof globInputPaths !== "boolean") {
errors.push("'globInputPaths' must be a boolean.");
@ -421,6 +416,9 @@ function compareResultsByFilePath(a, b) {
return 0;
}
/**
* Main API.
*/
class ESLint {
/**
@ -429,26 +427,13 @@ class ESLint {
*/
constructor(options = {}) {
const processedOptions = processOptions(options);
const cliEngine = new CLIEngine(processedOptions);
const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
const {
additionalPluginPool,
configArrayFactory,
lastConfigArrays
} = getCLIEngineInternalSlots(cliEngine);
let updated = false;
/*
* Address `plugins` to add plugin implementations.
* Operate the `additionalPluginPool` internal slot directly to avoid
* using `addPlugin(id, plugin)` method that resets cache everytime.
*/
if (options.plugins) {
for (const [id, plugin] of Object.entries(options.plugins)) {
additionalPluginPool.set(id, plugin);
updated = true;
}
}
/*
* Address `overrideConfig` to set override config.
* Operate the `configArrayFactory` internal slot directly because this
@ -529,6 +514,9 @@ class ESLint {
for (const { ruleId } of result.messages) {
resultRuleIds.add(ruleId);
}
for (const { ruleId } of result.suppressedMessages) {
resultRuleIds.add(ruleId);
}
}
// create a map of all rules in the results
@ -612,12 +600,12 @@ class ESLint {
* The following values are allowed:
* - `undefined` ... Load `stylish` builtin formatter.
* - A builtin formatter name ... Load the builtin formatter.
* - A thirdparty formatter name:
* - A third-party formatter name:
* - `foo` `eslint-formatter-foo`
* - `@foo` `@foo/eslint-formatter`
* - `@foo/bar` `@foo/eslint-formatter-bar`
* - A file path ... Load the file.
* @returns {Promise<Formatter>} A promise resolving to the formatter object.
* @returns {Promise<LoadedFormatter>} A promise resolving to the formatter object.
* This promise will be rejected if the given formatter was not found or not
* a function.
*/
@ -626,7 +614,7 @@ class ESLint {
throw new Error("'name' must be a string");
}
const { cliEngine } = privateMembersMap.get(this);
const { cliEngine, options } = privateMembersMap.get(this);
const formatter = cliEngine.getFormatter(name);
if (typeof formatter !== "function") {
@ -637,15 +625,20 @@ class ESLint {
/**
* The main formatter method.
* @param {LintResults[]} results The lint results to format.
* @returns {string} The formatted lint results.
* @param {LintResult[]} results The lint results to format.
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
* @returns {string | Promise<string>} The formatted lint results.
*/
format(results) {
format(results, resultsMeta) {
let rulesMeta = null;
results.sort(compareResultsByFilePath);
return formatter(results, {
...resultsMeta,
get cwd() {
return options.cwd;
},
get rulesMeta() {
if (!rulesMeta) {
rulesMeta = createRulesMeta(cliEngine.getRules());

1182
node_modules/eslint/lib/eslint/flat-eslint.js generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,9 @@
"use strict";
const { ESLint } = require("./eslint");
const { FlatESLint } = require("./flat-eslint");
module.exports = {
ESLint
ESLint,
FlatESLint
};

View file

@ -1,348 +0,0 @@
/**
* @fileoverview Used for creating a suggested configuration based on project code.
* @author Ian VanSchooten
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const equal = require("fast-deep-equal"),
recConfig = require("../../conf/eslint-recommended"),
ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
{ Linter } = require("../linter"),
configRule = require("./config-rule");
const debug = require("debug")("eslint:autoconfig");
const linter = new Linter();
//------------------------------------------------------------------------------
// Data
//------------------------------------------------------------------------------
const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only
RECOMMENDED_CONFIG_NAME = "eslint:recommended";
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/**
* Information about a rule configuration, in the context of a Registry.
* @typedef {Object} registryItem
* @param {ruleConfig} config A valid configuration for the rule
* @param {number} specificity The number of elements in the ruleConfig array
* @param {number} errorCount The number of errors encountered when linting with the config
*/
/**
* This callback is used to measure execution status in a progress bar
* @callback progressCallback
* @param {number} The total number of times the callback will be called.
*/
/**
* Create registryItems for rules
* @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items
* @returns {Object} registryItems for each rule in provided rulesConfig
*/
function makeRegistryItems(rulesConfig) {
return Object.keys(rulesConfig).reduce((accumulator, ruleId) => {
accumulator[ruleId] = rulesConfig[ruleId].map(config => ({
config,
specificity: config.length || 1,
errorCount: void 0
}));
return accumulator;
}, {});
}
/**
* Creates an object in which to store rule configs and error counts
*
* Unless a rulesConfig is provided at construction, the registry will not contain
* any rules, only methods. This will be useful for building up registries manually.
*
* Registry class
*/
class Registry {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
*/
constructor(rulesConfig) {
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
}
/**
* Populate the registry with core rule configs.
*
* It will set the registry's `rule` property to an object having rule names
* as keys and an array of registryItems as values.
* @returns {void}
*/
populateFromCoreRules() {
const rulesConfig = configRule.createCoreRuleConfigs(/* noDeprecated = */ true);
this.rules = makeRegistryItems(rulesConfig);
}
/**
* Creates sets of rule configurations which can be used for linting
* and initializes registry errors to zero for those configurations (side effect).
*
* This combines as many rules together as possible, such that the first sets
* in the array will have the highest number of rules configured, and later sets
* will have fewer and fewer, as not all rules have the same number of possible
* configurations.
*
* The length of the returned array will be <= MAX_CONFIG_COMBINATIONS.
* @returns {Object[]} "rules" configurations to use for linting
*/
buildRuleSets() {
let idx = 0;
const ruleIds = Object.keys(this.rules),
ruleSets = [];
/**
* Add a rule configuration from the registry to the ruleSets
*
* This is broken out into its own function so that it doesn't need to be
* created inside of the while loop.
* @param {string} rule The ruleId to add.
* @returns {void}
*/
const addRuleToRuleSet = function(rule) {
/*
* This check ensures that there is a rule configuration and that
* it has fewer than the max combinations allowed.
* If it has too many configs, we will only use the most basic of
* the possible configurations.
*/
const hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS);
if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) {
/*
* If the rule has too many possible combinations, only take
* simple ones, avoiding objects.
*/
if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") {
return;
}
ruleSets[idx] = ruleSets[idx] || {};
ruleSets[idx][rule] = this.rules[rule][idx].config;
/*
* Initialize errorCount to zero, since this is a config which
* will be linted.
*/
this.rules[rule][idx].errorCount = 0;
}
}.bind(this);
while (ruleSets.length === idx) {
ruleIds.forEach(addRuleToRuleSet);
idx += 1;
}
return ruleSets;
}
/**
* Remove all items from the registry with a non-zero number of errors
*
* Note: this also removes rule configurations which were not linted
* (meaning, they have an undefined errorCount).
* @returns {void}
*/
stripFailingConfigs() {
const ruleIds = Object.keys(this.rules),
newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(ruleId => {
const errorFreeItems = newRegistry.rules[ruleId].filter(registryItem => (registryItem.errorCount === 0));
if (errorFreeItems.length > 0) {
newRegistry.rules[ruleId] = errorFreeItems;
} else {
delete newRegistry.rules[ruleId];
}
});
return newRegistry;
}
/**
* Removes rule configurations which were not included in a ruleSet
* @returns {void}
*/
stripExtraConfigs() {
const ruleIds = Object.keys(this.rules),
newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(registryItem => (typeof registryItem.errorCount !== "undefined"));
});
return newRegistry;
}
/**
* Creates a registry of rules which had no error-free configs.
* The new registry is intended to be analyzed to determine whether its rules
* should be disabled or set to warning.
* @returns {Registry} A registry of failing rules.
*/
getFailingRulesRegistry() {
const ruleIds = Object.keys(this.rules),
failingRegistry = new Registry();
ruleIds.forEach(ruleId => {
const failingConfigs = this.rules[ruleId].filter(registryItem => (registryItem.errorCount > 0));
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
failingRegistry.rules[ruleId] = failingConfigs;
}
});
return failingRegistry;
}
/**
* Create an eslint config for any rules which only have one configuration
* in the registry.
* @returns {Object} An eslint config with rules section populated
*/
createConfig() {
const ruleIds = Object.keys(this.rules),
config = { rules: {} };
ruleIds.forEach(ruleId => {
if (this.rules[ruleId].length === 1) {
config.rules[ruleId] = this.rules[ruleId][0].config;
}
});
return config;
}
/**
* Return a cloned registry containing only configs with a desired specificity
* @param {number} specificity Only keep configs with this specificity
* @returns {Registry} A registry of rules
*/
filterBySpecificity(specificity) {
const ruleIds = Object.keys(this.rules),
newRegistry = new Registry();
newRegistry.rules = Object.assign({}, this.rules);
ruleIds.forEach(ruleId => {
newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity));
});
return newRegistry;
}
/**
* Lint SourceCodes against all configurations in the registry, and record results
* @param {Object[]} sourceCodes SourceCode objects for each filename
* @param {Object} config ESLint config object
* @param {progressCallback} [cb] Optional callback for reporting execution status
* @returns {Registry} New registry with errorCount populated
*/
lintSourceCode(sourceCodes, config, cb) {
let lintedRegistry = new Registry();
lintedRegistry.rules = Object.assign({}, this.rules);
const ruleSets = lintedRegistry.buildRuleSets();
lintedRegistry = lintedRegistry.stripExtraConfigs();
debug("Linting with all possible rule combinations");
const filenames = Object.keys(sourceCodes);
const totalFilesLinting = filenames.length * ruleSets.length;
filenames.forEach(filename => {
debug(`Linting file: ${filename}`);
let ruleSetIdx = 0;
ruleSets.forEach(ruleSet => {
const lintConfig = Object.assign({}, config, { rules: ruleSet });
const lintResults = linter.verify(sourceCodes[filename], lintConfig);
lintResults.forEach(result => {
/*
* It is possible that the error is from a configuration comment
* in a linted file, in which case there may not be a config
* set in this ruleSetIdx.
* (https://github.com/eslint/eslint/issues/5992)
* (https://github.com/eslint/eslint/issues/7860)
*/
if (
lintedRegistry.rules[result.ruleId] &&
lintedRegistry.rules[result.ruleId][ruleSetIdx]
) {
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
}
});
ruleSetIdx += 1;
if (cb) {
cb(totalFilesLinting); // eslint-disable-line node/callback-return
}
});
// Deallocate for GC
sourceCodes[filename] = null;
});
return lintedRegistry;
}
}
/**
* Extract rule configuration into eslint:recommended where possible.
*
* This will return a new config with `["extends": [ ..., "eslint:recommended"]` and
* only the rules which have configurations different from the recommended config.
* @param {Object} config config object
* @returns {Object} config object using `"extends": ["eslint:recommended"]`
*/
function extendFromRecommended(config) {
const newConfig = Object.assign({}, config);
ConfigOps.normalizeToStrings(newConfig);
const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
recRules.forEach(ruleId => {
if (equal(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
delete newConfig.rules[ruleId];
}
});
newConfig.extends.unshift(RECOMMENDED_CONFIG_NAME);
return newConfig;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
Registry,
extendFromRecommended
};

View file

@ -1,144 +0,0 @@
/**
* @fileoverview Helper to locate and load configuration files.
* @author Nicholas C. Zakas
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const fs = require("fs"),
path = require("path"),
stringify = require("json-stable-stringify-without-jsonify");
const debug = require("debug")("eslint:config-file");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Determines sort order for object keys for json-stable-stringify
*
* see: https://github.com/samn/json-stable-stringify#cmp
* @param {Object} a The first comparison object ({key: akey, value: avalue})
* @param {Object} b The second comparison object ({key: bkey, value: bvalue})
* @returns {number} 1 or -1, used in stringify cmp method
*/
function sortByKey(a, b) {
return a.key > b.key ? 1 : -1;
}
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/**
* Writes a configuration file in JSON format.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @private
*/
function writeJSONConfigFile(config, filePath) {
debug(`Writing JSON config file: ${filePath}`);
const content = `${stringify(config, { cmp: sortByKey, space: 4 })}\n`;
fs.writeFileSync(filePath, content, "utf8");
}
/**
* Writes a configuration file in YAML format.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @private
*/
function writeYAMLConfigFile(config, filePath) {
debug(`Writing YAML config file: ${filePath}`);
// lazy load YAML to improve performance when not used
const yaml = require("js-yaml");
const content = yaml.safeDump(config, { sortKeys: true });
fs.writeFileSync(filePath, content, "utf8");
}
/**
* Writes a configuration file in JavaScript format.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @throws {Error} If an error occurs linting the config file contents.
* @returns {void}
* @private
*/
function writeJSConfigFile(config, filePath) {
debug(`Writing JS config file: ${filePath}`);
let contentToWrite;
const stringifiedContent = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};\n`;
try {
const { CLIEngine } = require("../cli-engine");
const linter = new CLIEngine({
baseConfig: config,
fix: true,
useEslintrc: false
});
const report = linter.executeOnText(stringifiedContent);
contentToWrite = report.results[0].output || stringifiedContent;
} catch (e) {
debug("Error linting JavaScript config file, writing unlinted version");
const errorMessage = e.message;
contentToWrite = stringifiedContent;
e.message = "An error occurred while generating your JavaScript config file. ";
e.message += "A config file was still generated, but the config file itself may not follow your linting rules.";
e.message += `\nError: ${errorMessage}`;
throw e;
} finally {
fs.writeFileSync(filePath, contentToWrite, "utf8");
}
}
/**
* Writes a configuration file.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @throws {Error} When an unknown file type is specified.
* @private
*/
function write(config, filePath) {
switch (path.extname(filePath)) {
case ".js":
case ".cjs":
writeJSConfigFile(config, filePath);
break;
case ".json":
writeJSONConfigFile(config, filePath);
break;
case ".yaml":
case ".yml":
writeYAMLConfigFile(config, filePath);
break;
default:
throw new Error("Can't write to unknown file type.");
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
write
};

View file

@ -1,704 +0,0 @@
/**
* @fileoverview Config initialization wizard.
* @author Ilya Volodin
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const util = require("util"),
path = require("path"),
fs = require("fs"),
enquirer = require("enquirer"),
ProgressBar = require("progress"),
semver = require("semver"),
espree = require("espree"),
recConfig = require("../../conf/eslint-recommended"),
ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
log = require("../shared/logging"),
naming = require("@eslint/eslintrc/lib/shared/naming"),
ModuleResolver = require("../shared/relative-module-resolver"),
autoconfig = require("./autoconfig.js"),
ConfigFile = require("./config-file"),
npmUtils = require("./npm-utils"),
{ getSourceCodeOfFiles } = require("./source-code-utils");
const debug = require("debug")("eslint:config-initializer");
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/* istanbul ignore next: hard to test fs function */
/**
* Create .eslintrc file in the current working directory
* @param {Object} config object that contains user's answers
* @param {string} format The file format to write to.
* @returns {void}
*/
function writeFile(config, format) {
// default is .js
let extname = ".js";
if (format === "YAML") {
extname = ".yml";
} else if (format === "JSON") {
extname = ".json";
} else if (format === "JavaScript") {
const pkgJSONPath = npmUtils.findPackageJson();
if (pkgJSONPath) {
const pkgJSONContents = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8"));
if (pkgJSONContents.type === "module") {
extname = ".cjs";
}
}
}
const installedESLint = config.installedESLint;
delete config.installedESLint;
ConfigFile.write(config, `./.eslintrc${extname}`);
log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`);
if (installedESLint) {
log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy.");
}
}
/**
* Get the peer dependencies of the given module.
* This adds the gotten value to cache at the first time, then reuses it.
* In a process, this function is called twice, but `npmUtils.fetchPeerDependencies` needs to access network which is relatively slow.
* @param {string} moduleName The module name to get.
* @returns {Object} The peer dependencies of the given module.
* This object is the object of `peerDependencies` field of `package.json`.
* Returns null if npm was not found.
*/
function getPeerDependencies(moduleName) {
let result = getPeerDependencies.cache.get(moduleName);
if (!result) {
log.info(`Checking peerDependencies of ${moduleName}`);
result = npmUtils.fetchPeerDependencies(moduleName);
getPeerDependencies.cache.set(moduleName, result);
}
return result;
}
getPeerDependencies.cache = new Map();
/**
* Return necessary plugins, configs, parsers, etc. based on the config
* @param {Object} config config object
* @param {boolean} [installESLint=true] If `false` is given, it does not install eslint.
* @returns {string[]} An array of modules to be installed.
*/
function getModulesList(config, installESLint) {
const modules = {};
// Create a list of modules which should be installed based on config
if (config.plugins) {
for (const plugin of config.plugins) {
const moduleName = naming.normalizePackageName(plugin, "eslint-plugin");
modules[moduleName] = "latest";
}
}
if (config.extends) {
const extendList = Array.isArray(config.extends) ? config.extends : [config.extends];
for (const extend of extendList) {
if (extend.startsWith("eslint:") || extend.startsWith("plugin:")) {
continue;
}
const moduleName = naming.normalizePackageName(extend, "eslint-config");
modules[moduleName] = "latest";
Object.assign(
modules,
getPeerDependencies(`${moduleName}@latest`)
);
}
}
const parser = config.parser || (config.parserOptions && config.parserOptions.parser);
if (parser) {
modules[parser] = "latest";
}
if (installESLint === false) {
delete modules.eslint;
} else {
const installStatus = npmUtils.checkDevDeps(["eslint"]);
// Mark to show messages if it's new installation of eslint.
if (installStatus.eslint === false) {
log.info("Local ESLint installation not found.");
modules.eslint = modules.eslint || "latest";
config.installedESLint = true;
}
}
return Object.keys(modules).map(name => `${name}@${modules[name]}`);
}
/**
* Set the `rules` of a config by examining a user's source code
*
* Note: This clones the config object and returns a new config to avoid mutating
* the original config parameter.
* @param {Object} answers answers received from enquirer
* @param {Object} config config object
* @returns {Object} config object with configured rules
*/
function configureRules(answers, config) {
const BAR_TOTAL = 20,
BAR_SOURCE_CODE_TOTAL = 4,
newConfig = Object.assign({}, config),
disabledConfigs = {};
let sourceCodes,
registry;
// Set up a progress bar, as this process can take a long time
const bar = new ProgressBar("Determining Config: :percent [:bar] :elapseds elapsed, eta :etas ", {
width: 30,
total: BAR_TOTAL
});
bar.tick(0); // Shows the progress bar
// Get the SourceCode of all chosen files
const patterns = answers.patterns.split(/[\s]+/u);
try {
sourceCodes = getSourceCodeOfFiles(patterns, { baseConfig: newConfig, useEslintrc: false }, total => {
bar.tick((BAR_SOURCE_CODE_TOTAL / total));
});
} catch (e) {
log.info("\n");
throw e;
}
const fileQty = Object.keys(sourceCodes).length;
if (fileQty === 0) {
log.info("\n");
throw new Error("Automatic Configuration failed. No files were able to be parsed.");
}
// Create a registry of rule configs
registry = new autoconfig.Registry();
registry.populateFromCoreRules();
// Lint all files with each rule config in the registry
registry = registry.lintSourceCode(sourceCodes, newConfig, total => {
bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning
});
debug(`\nRegistry: ${util.inspect(registry.rules, { depth: null })}`);
// Create a list of recommended rules, because we don't want to disable them
const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
// Find and disable rules which had no error-free configuration
const failingRegistry = registry.getFailingRulesRegistry();
Object.keys(failingRegistry.rules).forEach(ruleId => {
// If the rule is recommended, set it to error, otherwise disable it
disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0;
});
// Now that we know which rules to disable, strip out configs with errors
registry = registry.stripFailingConfigs();
/*
* If there is only one config that results in no errors for a rule, we should use it.
* createConfig will only add rules that have one configuration in the registry.
*/
const singleConfigs = registry.createConfig().rules;
/*
* The "sweet spot" for number of options in a config seems to be two (severity plus one option).
* Very often, a third option (usually an object) is available to address
* edge cases, exceptions, or unique situations. We will prefer to use a config with
* specificity of two.
*/
const specTwoConfigs = registry.filterBySpecificity(2).createConfig().rules;
// Maybe a specific combination using all three options works
const specThreeConfigs = registry.filterBySpecificity(3).createConfig().rules;
// If all else fails, try to use the default (severity only)
const defaultConfigs = registry.filterBySpecificity(1).createConfig().rules;
// Combine configs in reverse priority order (later take precedence)
newConfig.rules = Object.assign({}, disabledConfigs, defaultConfigs, specThreeConfigs, specTwoConfigs, singleConfigs);
// Make sure progress bar has finished (floating point rounding)
bar.update(BAR_TOTAL);
// Log out some stats to let the user know what happened
const finalRuleIds = Object.keys(newConfig.rules);
const totalRules = finalRuleIds.length;
const enabledRules = finalRuleIds.filter(ruleId => (newConfig.rules[ruleId] !== 0)).length;
const resultMessage = [
`\nEnabled ${enabledRules} out of ${totalRules}`,
`rules based on ${fileQty}`,
`file${(fileQty === 1) ? "." : "s."}`
].join(" ");
log.info(resultMessage);
ConfigOps.normalizeToStrings(newConfig);
return newConfig;
}
/**
* process user's answers and create config object
* @param {Object} answers answers received from enquirer
* @returns {Object} config object
*/
function processAnswers(answers) {
let config = {
rules: {},
env: {},
parserOptions: {},
extends: []
};
config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
config.env.es2021 = true;
// set the module type
if (answers.moduleType === "esm") {
config.parserOptions.sourceType = "module";
} else if (answers.moduleType === "commonjs") {
config.env.commonjs = true;
}
// add in browser and node environments if necessary
answers.env.forEach(env => {
config.env[env] = true;
});
// add in library information
if (answers.framework === "react") {
config.parserOptions.ecmaFeatures = {
jsx: true
};
config.plugins = ["react"];
config.extends.push("plugin:react/recommended");
} else if (answers.framework === "vue") {
config.plugins = ["vue"];
config.extends.push("plugin:vue/essential");
}
if (answers.typescript) {
if (answers.framework === "vue") {
config.parserOptions.parser = "@typescript-eslint/parser";
} else {
config.parser = "@typescript-eslint/parser";
}
if (Array.isArray(config.plugins)) {
config.plugins.push("@typescript-eslint");
} else {
config.plugins = ["@typescript-eslint"];
}
}
// setup rules based on problems/style enforcement preferences
if (answers.purpose === "problems") {
config.extends.unshift("eslint:recommended");
} else if (answers.purpose === "style") {
if (answers.source === "prompt") {
config.extends.unshift("eslint:recommended");
config.rules.indent = ["error", answers.indent];
config.rules.quotes = ["error", answers.quotes];
config.rules["linebreak-style"] = ["error", answers.linebreak];
config.rules.semi = ["error", answers.semi ? "always" : "never"];
} else if (answers.source === "auto") {
config = configureRules(answers, config);
config = autoconfig.extendFromRecommended(config);
}
}
if (answers.typescript && config.extends.includes("eslint:recommended")) {
config.extends.push("plugin:@typescript-eslint/recommended");
}
// normalize extends
if (config.extends.length === 0) {
delete config.extends;
} else if (config.extends.length === 1) {
config.extends = config.extends[0];
}
ConfigOps.normalizeToStrings(config);
return config;
}
/**
* Get the version of the local ESLint.
* @returns {string|null} The version. If the local ESLint was not found, returns null.
*/
function getLocalESLintVersion() {
try {
const eslintPath = ModuleResolver.resolve("eslint", path.join(process.cwd(), "__placeholder__.js"));
const eslint = require(eslintPath);
return eslint.linter.version || null;
} catch {
return null;
}
}
/**
* Get the shareable config name of the chosen style guide.
* @param {Object} answers The answers object.
* @returns {string} The shareable config name.
*/
function getStyleGuideName(answers) {
if (answers.styleguide === "airbnb" && answers.framework !== "react") {
return "airbnb-base";
}
return answers.styleguide;
}
/**
* Check whether the local ESLint version conflicts with the required version of the chosen shareable config.
* @param {Object} answers The answers object.
* @returns {boolean} `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config.
*/
function hasESLintVersionConflict(answers) {
// Get the local ESLint version.
const localESLintVersion = getLocalESLintVersion();
if (!localESLintVersion) {
return false;
}
// Get the required range of ESLint version.
const configName = getStyleGuideName(answers);
const moduleName = `eslint-config-${configName}@latest`;
const peerDependencies = getPeerDependencies(moduleName) || {};
const requiredESLintVersionRange = peerDependencies.eslint;
if (!requiredESLintVersionRange) {
return false;
}
answers.localESLintVersion = localESLintVersion;
answers.requiredESLintVersionRange = requiredESLintVersionRange;
// Check the version.
if (semver.satisfies(localESLintVersion, requiredESLintVersionRange)) {
answers.installESLint = false;
return false;
}
return true;
}
/**
* Install modules.
* @param {string[]} modules Modules to be installed.
* @returns {void}
*/
function installModules(modules) {
log.info(`Installing ${modules.join(", ")}`);
npmUtils.installSyncSaveDev(modules);
}
/* istanbul ignore next: no need to test enquirer */
/**
* Ask user to install modules.
* @param {string[]} modules Array of modules to be installed.
* @param {boolean} packageJsonExists Indicates if package.json is existed.
* @returns {Promise} Answer that indicates if user wants to install.
*/
function askInstallModules(modules, packageJsonExists) {
// If no modules, do nothing.
if (modules.length === 0) {
return Promise.resolve();
}
log.info("The config that you've selected requires the following dependencies:\n");
log.info(modules.join(" "));
return enquirer.prompt([
{
type: "toggle",
name: "executeInstallation",
message: "Would you like to install them now with npm?",
enabled: "Yes",
disabled: "No",
initial: 1,
skip() {
return !(modules.length && packageJsonExists);
},
result(input) {
return this.skipped ? null : input;
}
}
]).then(({ executeInstallation }) => {
if (executeInstallation) {
installModules(modules);
}
});
}
/* istanbul ignore next: no need to test enquirer */
/**
* Ask use a few questions on command prompt
* @returns {Promise} The promise with the result of the prompt
*/
function promptUser() {
return enquirer.prompt([
{
type: "select",
name: "purpose",
message: "How would you like to use ESLint?",
// The returned number matches the name value of nth in the choices array.
initial: 1,
choices: [
{ message: "To check syntax only", name: "syntax" },
{ message: "To check syntax and find problems", name: "problems" },
{ message: "To check syntax, find problems, and enforce code style", name: "style" }
]
},
{
type: "select",
name: "moduleType",
message: "What type of modules does your project use?",
initial: 0,
choices: [
{ message: "JavaScript modules (import/export)", name: "esm" },
{ message: "CommonJS (require/exports)", name: "commonjs" },
{ message: "None of these", name: "none" }
]
},
{
type: "select",
name: "framework",
message: "Which framework does your project use?",
initial: 0,
choices: [
{ message: "React", name: "react" },
{ message: "Vue.js", name: "vue" },
{ message: "None of these", name: "none" }
]
},
{
type: "toggle",
name: "typescript",
message: "Does your project use TypeScript?",
enabled: "Yes",
disabled: "No",
initial: 0
},
{
type: "multiselect",
name: "env",
message: "Where does your code run?",
hint: "(Press <space> to select, <a> to toggle all, <i> to invert selection)",
initial: 0,
choices: [
{ message: "Browser", name: "browser" },
{ message: "Node", name: "node" }
]
},
{
type: "select",
name: "source",
message: "How would you like to define a style for your project?",
choices: [
{ message: "Use a popular style guide", name: "guide" },
{ message: "Answer questions about your style", name: "prompt" },
{ message: "Inspect your JavaScript file(s)", name: "auto" }
],
skip() {
return this.state.answers.purpose !== "style";
},
result(input) {
return this.skipped ? null : input;
}
},
{
type: "select",
name: "styleguide",
message: "Which style guide do you want to follow?",
choices: [
{ message: "Airbnb: https://github.com/airbnb/javascript", name: "airbnb" },
{ message: "Standard: https://github.com/standard/standard", name: "standard" },
{ message: "Google: https://github.com/google/eslint-config-google", name: "google" },
{ message: "XO: https://github.com/xojs/eslint-config-xo", name: "xo" }
],
skip() {
this.state.answers.packageJsonExists = npmUtils.checkPackageJson();
return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists);
},
result(input) {
return this.skipped ? null : input;
}
},
{
type: "input",
name: "patterns",
message: "Which file(s), path(s), or glob(s) should be examined?",
skip() {
return this.state.answers.source !== "auto";
},
validate(input) {
if (!this.skipped && input.trim().length === 0 && input.trim() !== ",") {
return "You must tell us what code to examine. Try again.";
}
return true;
}
},
{
type: "select",
name: "format",
message: "What format do you want your config file to be in?",
initial: 0,
choices: ["JavaScript", "YAML", "JSON"]
},
{
type: "toggle",
name: "installESLint",
message() {
const { answers } = this.state;
const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange)
? "upgrade"
: "downgrade";
return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n Do you want to ${verb}?`;
},
enabled: "Yes",
disabled: "No",
initial: 1,
skip() {
return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists && hasESLintVersionConflict(this.state.answers));
},
result(input) {
return this.skipped ? null : input;
}
}
]).then(earlyAnswers => {
// early exit if no style guide is necessary
if (earlyAnswers.purpose !== "style") {
const config = processAnswers(earlyAnswers);
const modules = getModulesList(config);
return askInstallModules(modules, earlyAnswers.packageJsonExists)
.then(() => writeFile(config, earlyAnswers.format));
}
// early exit if you are using a style guide
if (earlyAnswers.source === "guide") {
if (!earlyAnswers.packageJsonExists) {
log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again.");
return void 0;
}
if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) {
log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`);
}
if (earlyAnswers.styleguide === "airbnb" && earlyAnswers.framework !== "react") {
earlyAnswers.styleguide = "airbnb-base";
}
const config = processAnswers(earlyAnswers);
if (Array.isArray(config.extends)) {
config.extends.push(earlyAnswers.styleguide);
} else if (config.extends) {
config.extends = [config.extends, earlyAnswers.styleguide];
} else {
config.extends = [earlyAnswers.styleguide];
}
const modules = getModulesList(config);
return askInstallModules(modules, earlyAnswers.packageJsonExists)
.then(() => writeFile(config, earlyAnswers.format));
}
if (earlyAnswers.source === "auto") {
const combinedAnswers = Object.assign({}, earlyAnswers);
const config = processAnswers(combinedAnswers);
const modules = getModulesList(config);
return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format));
}
// continue with the style questions otherwise...
return enquirer.prompt([
{
type: "select",
name: "indent",
message: "What style of indentation do you use?",
initial: 0,
choices: [{ message: "Tabs", name: "tab" }, { message: "Spaces", name: 4 }]
},
{
type: "select",
name: "quotes",
message: "What quotes do you use for strings?",
initial: 0,
choices: [{ message: "Double", name: "double" }, { message: "Single", name: "single" }]
},
{
type: "select",
name: "linebreak",
message: "What line endings do you use?",
initial: 0,
choices: [{ message: "Unix", name: "unix" }, { message: "Windows", name: "windows" }]
},
{
type: "toggle",
name: "semi",
message: "Do you require semicolons?",
enabled: "Yes",
disabled: "No",
initial: 1
}
]).then(answers => {
const totalAnswers = Object.assign({}, earlyAnswers, answers);
const config = processAnswers(totalAnswers);
const modules = getModulesList(config);
return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format));
});
});
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
const init = {
getModulesList,
hasESLintVersionConflict,
installModules,
processAnswers,
writeFile,
/* istanbul ignore next */initializeConfig() {
return promptUser();
}
};
module.exports = init;

View file

@ -1,317 +0,0 @@
/**
* @fileoverview Create configurations for a rule
* @author Ian VanSchooten
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const builtInRules = require("../rules");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Wrap all of the elements of an array into arrays.
* @param {*[]} xs Any array.
* @returns {Array[]} An array of arrays.
*/
function explodeArray(xs) {
return xs.reduce((accumulator, x) => {
accumulator.push([x]);
return accumulator;
}, []);
}
/**
* Mix two arrays such that each element of the second array is concatenated
* onto each element of the first array.
*
* For example:
* combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]]
* @param {Array} arr1 The first array to combine.
* @param {Array} arr2 The second array to combine.
* @returns {Array} A mixture of the elements of the first and second arrays.
*/
function combineArrays(arr1, arr2) {
const res = [];
if (arr1.length === 0) {
return explodeArray(arr2);
}
if (arr2.length === 0) {
return explodeArray(arr1);
}
arr1.forEach(x1 => {
arr2.forEach(x2 => {
res.push([].concat(x1, x2));
});
});
return res;
}
/**
* Group together valid rule configurations based on object properties
*
* e.g.:
* groupByProperty([
* {before: true},
* {before: false},
* {after: true},
* {after: false}
* ]);
*
* will return:
* [
* [{before: true}, {before: false}],
* [{after: true}, {after: false}]
* ]
* @param {Object[]} objects Array of objects, each with one property/value pair
* @returns {Array[]} Array of arrays of objects grouped by property
*/
function groupByProperty(objects) {
const groupedObj = objects.reduce((accumulator, obj) => {
const prop = Object.keys(obj)[0];
accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj];
return accumulator;
}, {});
return Object.keys(groupedObj).map(prop => groupedObj[prop]);
}
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/**
* Configuration settings for a rule.
*
* A configuration can be a single number (severity), or an array where the first
* element in the array is the severity, and is the only required element.
* Configs may also have one or more additional elements to specify rule
* configuration or options.
* @typedef {Array|number} ruleConfig
* @param {number} 0 The rule's severity (0, 1, 2).
*/
/**
* Object whose keys are rule names and values are arrays of valid ruleConfig items
* which should be linted against the target source code to determine error counts.
* (a ruleConfigSet.ruleConfigs).
*
* e.g. rulesConfig = {
* "comma-dangle": [2, [2, "always"], [2, "always-multiline"], [2, "never"]],
* "no-console": [2]
* }
* @typedef rulesConfig
*/
/**
* Create valid rule configurations by combining two arrays,
* with each array containing multiple objects each with a
* single property/value pair and matching properties.
*
* e.g.:
* combinePropertyObjects(
* [{before: true}, {before: false}],
* [{after: true}, {after: false}]
* );
*
* will return:
* [
* {before: true, after: true},
* {before: true, after: false},
* {before: false, after: true},
* {before: false, after: false}
* ]
* @param {Object[]} objArr1 Single key/value objects, all with the same key
* @param {Object[]} objArr2 Single key/value objects, all with another key
* @returns {Object[]} Combined objects for each combination of input properties and values
*/
function combinePropertyObjects(objArr1, objArr2) {
const res = [];
if (objArr1.length === 0) {
return objArr2;
}
if (objArr2.length === 0) {
return objArr1;
}
objArr1.forEach(obj1 => {
objArr2.forEach(obj2 => {
const combinedObj = {};
const obj1Props = Object.keys(obj1);
const obj2Props = Object.keys(obj2);
obj1Props.forEach(prop1 => {
combinedObj[prop1] = obj1[prop1];
});
obj2Props.forEach(prop2 => {
combinedObj[prop2] = obj2[prop2];
});
res.push(combinedObj);
});
});
return res;
}
/**
* Creates a new instance of a rule configuration set
*
* A rule configuration set is an array of configurations that are valid for a
* given rule. For example, the configuration set for the "semi" rule could be:
*
* ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
*
* Rule configuration set class
*/
class RuleConfigSet {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {ruleConfig[]} configs Valid rule configurations
*/
constructor(configs) {
/**
* Stored valid rule configurations for this instance
* @type {Array}
*/
this.ruleConfigs = configs || [];
}
/**
* 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.
* @returns {void}
*/
addErrorSeverity() {
const severity = 2;
this.ruleConfigs = this.ruleConfigs.map(config => {
config.unshift(severity);
return config;
});
// Add a single config at the beginning consisting of only the severity
this.ruleConfigs.unshift(severity);
}
/**
* Add rule configs from an array of strings (schema enums)
* @param {string[]} enums Array of valid rule options (e.g. ["always", "never"])
* @returns {void}
*/
addEnums(enums) {
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums));
}
/**
* Add rule configurations from a schema object
* @param {Object} obj Schema item with type === "object"
* @returns {boolean} true if at least one schema for the object could be generated, false otherwise
*/
addObject(obj) {
const objectConfigSet = {
objectConfigs: [],
add(property, values) {
for (let idx = 0; idx < values.length; idx++) {
const optionObj = {};
optionObj[property] = values[idx];
this.objectConfigs.push(optionObj);
}
},
combine() {
this.objectConfigs = groupByProperty(this.objectConfigs).reduce((accumulator, objArr) => combinePropertyObjects(accumulator, objArr), []);
}
};
/*
* The object schema could have multiple independent properties.
* If any contain enums or booleans, they can be added and then combined
*/
Object.keys(obj.properties).forEach(prop => {
if (obj.properties[prop].enum) {
objectConfigSet.add(prop, obj.properties[prop].enum);
}
if (obj.properties[prop].type && obj.properties[prop].type === "boolean") {
objectConfigSet.add(prop, [true, false]);
}
});
objectConfigSet.combine();
if (objectConfigSet.objectConfigs.length > 0) {
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs));
return true;
}
return false;
}
}
/**
* Generate valid rule configurations based on a schema object
* @param {Object} schema A rule's schema object
* @returns {Array[]} Valid rule configurations
*/
function generateConfigsFromSchema(schema) {
const configSet = new RuleConfigSet();
if (Array.isArray(schema)) {
for (const opt of schema) {
if (opt.enum) {
configSet.addEnums(opt.enum);
} else if (opt.type && opt.type === "object") {
if (!configSet.addObject(opt)) {
break;
}
// TODO (IanVS): support oneOf
} else {
// If we don't know how to fill in this option, don't fill in any of the following options.
break;
}
}
}
configSet.addErrorSeverity();
return configSet.ruleConfigs;
}
/**
* Generate possible rule configurations for all of the core rules
* @param {boolean} noDeprecated Indicates whether ignores deprecated rules or not.
* @returns {rulesConfig} Hash of rule names and arrays of possible configurations
*/
function createCoreRuleConfigs(noDeprecated = false) {
return Array.from(builtInRules).reduce((accumulator, [id, rule]) => {
const schema = (typeof rule === "function") ? rule.schema : rule.meta.schema;
const isDeprecated = (typeof rule === "function") ? rule.deprecated : rule.meta.deprecated;
if (noDeprecated && isDeprecated) {
return accumulator;
}
accumulator[id] = generateConfigsFromSchema(schema);
return accumulator;
}, {});
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
generateConfigsFromSchema,
createCoreRuleConfigs
};

View file

@ -1,178 +0,0 @@
/**
* @fileoverview Utility for executing npm commands.
* @author Ian VanSchooten
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const fs = require("fs"),
spawn = require("cross-spawn"),
path = require("path"),
log = require("../shared/logging");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Find the closest package.json file, starting at process.cwd (by default),
* and working up to root.
* @param {string} [startDir=process.cwd()] Starting directory
* @returns {string} Absolute path to closest package.json file
*/
function findPackageJson(startDir) {
let dir = path.resolve(startDir || process.cwd());
do {
const pkgFile = path.join(dir, "package.json");
if (!fs.existsSync(pkgFile) || !fs.statSync(pkgFile).isFile()) {
dir = path.join(dir, "..");
continue;
}
return pkgFile;
} while (dir !== path.resolve(dir, ".."));
return null;
}
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/**
* Install node modules synchronously and save to devDependencies in package.json
* @param {string|string[]} packages Node module or modules to install
* @returns {void}
*/
function installSyncSaveDev(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 = packageList.length > 1 ? "s" : "";
log.error(`Could not execute npm. Please install the following package${pluralS} with a package manager of your choice: ${packageList.join(", ")}`);
}
}
/**
* Fetch `peerDependencies` of the given package by `npm show` command.
* @param {string} packageName The package name to fetch peerDependencies.
* @returns {Object} Gotten peerDependencies. Returns null if npm was not found.
*/
function fetchPeerDependencies(packageName) {
const npmProcess = spawn.sync(
"npm",
["show", "--json", packageName, "peerDependencies"],
{ encoding: "utf8" }
);
const error = npmProcess.error;
if (error && error.code === "ENOENT") {
return null;
}
const fetchedText = npmProcess.stdout.trim();
return JSON.parse(fetchedText || "{}");
}
/**
* Check whether node modules are include in a project's package.json.
* @param {string[]} packages Array of node module names
* @param {Object} opt Options Object
* @param {boolean} opt.dependencies Set to true to check for direct dependencies
* @param {boolean} opt.devDependencies Set to true to check for development dependencies
* @param {boolean} opt.startdir Directory to begin searching from
* @returns {Object} An object whose keys are the module names
* and values are booleans indicating installation.
*/
function check(packages, opt) {
const deps = new Set();
const pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson();
let fileJson;
if (!pkgJson) {
throw new Error("Could not find a package.json file. Run 'npm init' to create one.");
}
try {
fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
} catch (e) {
const error = new Error(e);
error.messageTemplate = "failed-to-read-json";
error.messageData = {
path: pkgJson,
message: e.message
};
throw error;
}
["dependencies", "devDependencies"].forEach(key => {
if (opt[key] && typeof fileJson[key] === "object") {
Object.keys(fileJson[key]).forEach(dep => deps.add(dep));
}
});
return packages.reduce((status, pkg) => {
status[pkg] = deps.has(pkg);
return status;
}, {});
}
/**
* Check whether node modules are included in the dependencies of a project's
* package.json.
*
* Convenience wrapper around check().
* @param {string[]} packages Array of node modules to check.
* @param {string} rootDir The directory containing a package.json
* @returns {Object} An object whose keys are the module names
* and values are booleans indicating installation.
*/
function checkDeps(packages, rootDir) {
return check(packages, { dependencies: true, startDir: rootDir });
}
/**
* Check whether node modules are included in the devDependencies of a project's
* package.json.
*
* Convenience wrapper around check().
* @param {string[]} packages Array of node modules to check.
* @returns {Object} An object whose keys are the module names
* and values are booleans indicating installation.
*/
function checkDevDeps(packages) {
return check(packages, { devDependencies: true });
}
/**
* Check whether package.json is found in current path.
* @param {string} [startDir] Starting directory
* @returns {boolean} Whether a package.json is found in current path.
*/
function checkPackageJson(startDir) {
return !!findPackageJson(startDir);
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
installSyncSaveDev,
fetchPeerDependencies,
findPackageJson,
checkDeps,
checkDevDeps,
checkPackageJson
};

View file

@ -1,109 +0,0 @@
/**
* @fileoverview Tools for obtaining SourceCode objects.
* @author Ian VanSchooten
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const { CLIEngine } = require("../cli-engine");
/*
* This is used for:
*
* 1. Enumerate target file because we have not expose such a API on `CLIEngine`
* (https://github.com/eslint/eslint/issues/11222).
* 2. Create `SourceCode` instances. Because we don't have any function which
* instantiate `SourceCode` so it needs to take the created `SourceCode`
* instance out after linting.
*
* TODO1: Expose the API that enumerates target files.
* TODO2: Extract the creation logic of `SourceCode` from `Linter` class.
*/
const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require
const debug = require("debug")("eslint:source-code-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Get the SourceCode object for a single file
* @param {string} filename The fully resolved filename to get SourceCode from.
* @param {Object} engine A CLIEngine.
* @returns {Array} Array of the SourceCode object representing the file
* and fatal error message.
*/
function getSourceCodeOfFile(filename, engine) {
debug("getting sourceCode of", filename);
const results = engine.executeOnFiles([filename]);
if (results && results.results[0] && results.results[0].messages[0] && results.results[0].messages[0].fatal) {
const msg = results.results[0].messages[0];
throw new Error(`(${filename}:${msg.line}:${msg.column}) ${msg.message}`);
}
// TODO: extract the logic that creates source code objects to `SourceCode#parse(text, options)` or something like.
const { linter } = getCLIEngineInternalSlots(engine);
const sourceCode = linter.getSourceCode();
return sourceCode;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* This callback is used to measure execution status in a progress bar
* @callback progressCallback
* @param {number} The total number of times the callback will be called.
*/
/**
* 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} callback Callback for reporting execution status
* @returns {Object} The SourceCode of all processed files.
*/
function getSourceCodeOfFiles(patterns, options, callback) {
const sourceCodes = {};
const globPatternsList = typeof patterns === "string" ? [patterns] : patterns;
const engine = new CLIEngine({ ...options, rules: {} });
// TODO: make file iteration as a public API and use it.
const { fileEnumerator } = getCLIEngineInternalSlots(engine);
const filenames =
Array.from(fileEnumerator.iterateFiles(globPatternsList))
.filter(entry => !entry.ignored)
.map(entry => entry.filePath);
if (filenames.length === 0) {
debug(`Did not find any files matching pattern(s): ${globPatternsList}`);
}
filenames.forEach(filename => {
const sourceCode = getSourceCodeOfFile(filename, engine);
if (sourceCode) {
debug("got sourceCode of", filename);
sourceCodes[filename] = sourceCode;
}
if (callback) {
callback(filenames.length); // eslint-disable-line node/callback-return
}
});
return sourceCodes;
}
module.exports = {
getSourceCodeOfFiles
};

View file

@ -5,6 +5,8 @@
"use strict";
const escapeRegExp = require("escape-string-regexp");
/**
* Compares the locations of two objects in a source file
* @param {{line: number, column: number}} itemA The first object
@ -16,6 +18,177 @@ function compareLocations(itemA, itemB) {
return itemA.line - itemB.line || itemA.column - itemB.column;
}
/**
* Groups a set of directives into sub-arrays by their parent comment.
* @param {Directive[]} directives Unused directives to be removed.
* @returns {Directive[][]} Directives grouped by their parent comment.
*/
function groupByParentComment(directives) {
const groups = new Map();
for (const directive of directives) {
const { unprocessedDirective: { parentComment } } = directive;
if (groups.has(parentComment)) {
groups.get(parentComment).push(directive);
} else {
groups.set(parentComment, [directive]);
}
}
return [...groups.values()];
}
/**
* Creates removal details for a set of directives within the same comment.
* @param {Directive[]} directives Unused directives to be removed.
* @param {Token} commentToken The backing Comment token.
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
*/
function createIndividualDirectivesRemoval(directives, commentToken) {
/*
* `commentToken.value` starts right after `//` or `/*`.
* All calculated offsets will be relative to this index.
*/
const commentValueStart = commentToken.range[0] + "//".length;
// Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length;
/*
* Get the list text without any surrounding whitespace. In order to preserve the original
* formatting, we don't want to change that whitespace.
*
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*/
const listText = commentToken.value
.slice(listStartOffset) // remove directive name and all whitespace before the list
.split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists
.trimEnd(); // remove all whitespace after the list
/*
* We can assume that `listText` contains multiple elements.
* Otherwise, this function wouldn't be called - if there is
* only one rule in the list, then the whole comment must be removed.
*/
return directives.map(directive => {
const { ruleId } = directive;
const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u");
const match = regex.exec(listText);
const matchedText = match[0];
const matchStartOffset = listStartOffset + match.index;
const matchEndOffset = matchStartOffset + matchedText.length;
const firstIndexOfComma = matchedText.indexOf(",");
const lastIndexOfComma = matchedText.lastIndexOf(",");
let removalStartOffset, removalEndOffset;
if (firstIndexOfComma !== lastIndexOfComma) {
/*
* Since there are two commas, this must one of the elements in the middle of the list.
* Matched range starts where the previous rule name ends, and ends where the next rule name starts.
*
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
* ^^^^^^^^^^^^^^
*
* We want to remove only the content between the two commas, and also one of the commas.
*
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
* ^^^^^^^^^^^
*/
removalStartOffset = matchStartOffset + firstIndexOfComma;
removalEndOffset = matchStartOffset + lastIndexOfComma;
} else {
/*
* This is either the first element or the last element.
*
* If this is the first element, matched range starts where the first rule name starts
* and ends where the second rule name starts. This is exactly the range we want
* to remove so that the second rule name will start where the first one was starting
* and thus preserve the original formatting.
*
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
* ^^^^^^^^^^^
*
* Similarly, if this is the last element, we've already matched the range we want to
* remove. The previous rule name will end where the last one was ending, relative
* to the content on the right side.
*
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
* ^^^^^^^^^^^^^
*/
removalStartOffset = matchStartOffset;
removalEndOffset = matchEndOffset;
}
return {
description: `'${ruleId}'`,
fix: {
range: [
commentValueStart + removalStartOffset,
commentValueStart + removalEndOffset
],
text: ""
},
unprocessedDirective: directive.unprocessedDirective
};
});
}
/**
* Creates a description of deleting an entire unused disable comment.
* @param {Directive[]} directives Unused directives to be removed.
* @param {Token} commentToken The backing Comment token.
* @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output Problem.
*/
function createCommentRemoval(directives, commentToken) {
const { range } = commentToken;
const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`);
return {
description: ruleIds.length <= 2
? ruleIds.join(" or ")
: `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds[ruleIds.length - 1]}`,
fix: {
range,
text: " "
},
unprocessedDirective: directives[0].unprocessedDirective
};
}
/**
* Parses details from directives to create output Problems.
* @param {Directive[]} allDirectives Unused directives to be removed.
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
*/
function processUnusedDisableDirectives(allDirectives) {
const directiveGroups = groupByParentComment(allDirectives);
return directiveGroups.flatMap(
directives => {
const { parentComment } = directives[0].unprocessedDirective;
const remainingRuleIds = new Set(parentComment.ruleIds);
for (const directive of directives) {
remainingRuleIds.delete(directive.ruleId);
}
return remainingRuleIds.size
? createIndividualDirectivesRemoval(directives, parentComment.commentToken)
: [createCommentRemoval(directives, parentComment.commentToken)];
}
);
}
/**
* This is the same as the exported function, except that it
* doesn't handle disable-line and disable-next-line directives, and it always reports unused
@ -24,119 +197,106 @@ function compareLocations(itemA, itemB) {
* for the exported function, except that `reportUnusedDisableDirectives` is not supported
* (this function always reports unused disable directives).
* @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
* of filtered problems and unused eslint-disable directives
* of problems (including suppressed ones) and unused eslint-disable directives
*/
function applyDirectives(options) {
const problems = [];
let nextDirectiveIndex = 0;
let currentGlobalDisableDirective = null;
const disabledRuleMap = new Map();
// enabledRules is only used when there is a current global disable directive.
const enabledRules = new Set();
const usedDisableDirectives = new Set();
for (const problem of options.problems) {
let disableDirectivesForProblem = [];
let nextDirectiveIndex = 0;
while (
nextDirectiveIndex < options.directives.length &&
compareLocations(options.directives[nextDirectiveIndex], problem) <= 0
) {
const directive = options.directives[nextDirectiveIndex++];
switch (directive.type) {
case "disable":
if (directive.ruleId === null) {
currentGlobalDisableDirective = directive;
disabledRuleMap.clear();
enabledRules.clear();
} else if (currentGlobalDisableDirective) {
enabledRules.delete(directive.ruleId);
disabledRuleMap.set(directive.ruleId, directive);
} else {
disabledRuleMap.set(directive.ruleId, directive);
}
break;
if (directive.ruleId === null || directive.ruleId === problem.ruleId) {
switch (directive.type) {
case "disable":
disableDirectivesForProblem.push(directive);
break;
case "enable":
if (directive.ruleId === null) {
currentGlobalDisableDirective = null;
disabledRuleMap.clear();
} else if (currentGlobalDisableDirective) {
enabledRules.add(directive.ruleId);
disabledRuleMap.delete(directive.ruleId);
} else {
disabledRuleMap.delete(directive.ruleId);
}
break;
case "enable":
disableDirectivesForProblem = [];
break;
// no default
// no default
}
}
}
if (disabledRuleMap.has(problem.ruleId)) {
usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId));
} else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) {
usedDisableDirectives.add(currentGlobalDisableDirective);
} else {
problems.push(problem);
if (disableDirectivesForProblem.length > 0) {
const suppressions = disableDirectivesForProblem.map(directive => ({
kind: "directive",
justification: directive.unprocessedDirective.justification
}));
if (problem.suppressions) {
problem.suppressions = problem.suppressions.concat(suppressions);
} else {
problem.suppressions = suppressions;
usedDisableDirectives.add(disableDirectivesForProblem[disableDirectivesForProblem.length - 1]);
}
}
problems.push(problem);
}
const unusedDisableDirectives = options.directives
.filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive))
.map(directive => ({
ruleId: null,
message: directive.ruleId
? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').`
: "Unused eslint-disable directive (no problems were reported).",
line: directive.unprocessedDirective.line,
column: directive.unprocessedDirective.column,
severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
nodeType: null
}));
const unusedDisableDirectivesToReport = options.directives
.filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive));
const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport);
const unusedDisableDirectives = processed
.map(({ description, fix, unprocessedDirective }) => {
const { parentComment, type, line, column } = unprocessedDirective;
return {
ruleId: null,
message: description
? `Unused eslint-disable directive (no problems were reported from ${description}).`
: "Unused eslint-disable directive (no problems were reported).",
line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line,
column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column,
severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
nodeType: null,
...options.disableFixes ? {} : { fix }
};
});
return { problems, unusedDisableDirectives };
}
/**
* Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
* of reported problems, determines which problems should be reported.
* of reported problems, adds the suppression information to the problems.
* @param {Object} options Information about directives and problems
* @param {{
* type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
* ruleId: (string|null),
* line: number,
* column: number
* column: number,
* justification: string
* }} options.directives Directive comments found in the file, with one-based columns.
* Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
* comment for two different rules is represented as two directives).
* @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
* A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
* @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
* @returns {{ruleId: (string|null), line: number, column: number}[]}
* A list of reported problems that were not disabled by the directive comments.
* @param {boolean} options.disableFixes If true, it doesn't make `fix` properties.
* @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]}
* An object with a list of reported problems, the suppressed of which contain the suppression information.
*/
module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off" }) => {
module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => {
const blockDirectives = directives
.filter(directive => directive.type === "disable" || directive.type === "enable")
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
.sort(compareLocations);
/**
* Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
* TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
* @param {any[]} array The array to process
* @param {Function} fn The function to use
* @returns {any[]} The result array
*/
function flatMap(array, fn) {
const mapped = array.map(fn);
const flattened = [].concat(...mapped);
return flattened;
}
const lineDirectives = flatMap(directives, directive => {
const lineDirectives = directives.flatMap(directive => {
switch (directive.type) {
case "disable":
case "enable":
@ -162,11 +322,13 @@ module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off"
const blockDirectivesResult = applyDirectives({
problems,
directives: blockDirectives,
disableFixes,
reportUnusedDisableDirectives
});
const lineDirectivesResult = applyDirectives({
problems: blockDirectivesResult.problems,
directives: lineDirectives,
disableFixes,
reportUnusedDisableDirectives
});

View file

@ -29,6 +29,18 @@ function isCaseNode(node) {
return Boolean(node.test);
}
/**
* Checks if a given node appears as the value of a PropertyDefinition node.
* @param {ASTNode} node THe node to check.
* @returns {boolean} `true` if the node is a PropertyDefinition value,
* false if not.
*/
function isPropertyDefinitionValue(node) {
const parent = node.parent;
return parent && parent.type === "PropertyDefinition" && parent.value === node;
}
/**
* Checks whether the given logical operator is taken into account for the code
* path analysis.
@ -138,6 +150,7 @@ function isIdentifierReference(node) {
return parent.id !== node;
case "Property":
case "PropertyDefinition":
case "MethodDefinition":
return (
parent.key !== node ||
@ -388,29 +401,68 @@ function processCodePathToEnter(analyzer, node) {
let state = codePath && CodePath.getState(codePath);
const parent = node.parent;
/**
* Creates a new code path and trigger the onCodePathStart event
* based on the currently selected node.
* @param {string} origin The reason the code path was started.
* @returns {void}
*/
function startCodePath(origin) {
if (codePath) {
// Emits onCodePathSegmentStart events if updated.
forwardCurrentToHead(analyzer, node);
debug.dumpState(node, state, false);
}
// Create the code path of this scope.
codePath = analyzer.codePath = new CodePath({
id: analyzer.idGenerator.next(),
origin,
upper: codePath,
onLooped: analyzer.onLooped
});
state = CodePath.getState(codePath);
// Emits onCodePathStart events.
debug.dump(`onCodePathStart ${codePath.id}`);
analyzer.emitter.emit("onCodePathStart", codePath, node);
}
/*
* Special case: The right side of class field initializer is considered
* to be its own function, so we need to start a new code path in this
* case.
*/
if (isPropertyDefinitionValue(node)) {
startCodePath("class-field-initializer");
/*
* Intentional fall through because `node` needs to also be
* processed by the code below. For example, if we have:
*
* class Foo {
* a = () => {}
* }
*
* In this case, we also need start a second code path.
*/
}
switch (node.type) {
case "Program":
startCodePath("program");
break;
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
if (codePath) {
startCodePath("function");
break;
// Emits onCodePathSegmentStart events if updated.
forwardCurrentToHead(analyzer, node);
debug.dumpState(node, state, false);
}
// Create the code path of this scope.
codePath = analyzer.codePath = new CodePath(
analyzer.idGenerator.next(),
codePath,
analyzer.onLooped
);
state = CodePath.getState(codePath);
// Emits onCodePathStart events.
debug.dump(`onCodePathStart ${codePath.id}`);
analyzer.emitter.emit("onCodePathStart", codePath, node);
case "StaticBlock":
startCodePath("class-static-block");
break;
case "ChainExpression":
@ -503,6 +555,7 @@ function processCodePathToEnter(analyzer, node) {
* @returns {void}
*/
function processCodePathToExit(analyzer, node) {
const codePath = analyzer.codePath;
const state = CodePath.getState(codePath);
let dontForward = false;
@ -627,28 +680,39 @@ function processCodePathToExit(analyzer, node) {
* @returns {void}
*/
function postprocess(analyzer, node) {
/**
* Ends the code path for the current node.
* @returns {void}
*/
function endCodePath() {
let codePath = analyzer.codePath;
// Mark the current path as the final node.
CodePath.getState(codePath).makeFinal();
// Emits onCodePathSegmentEnd event of the current segments.
leaveFromCurrentSegment(analyzer, node);
// Emits onCodePathEnd event of this code path.
debug.dump(`onCodePathEnd ${codePath.id}`);
analyzer.emitter.emit("onCodePathEnd", codePath, node);
debug.dumpDot(codePath);
codePath = analyzer.codePath = analyzer.codePath.upper;
if (codePath) {
debug.dumpState(node, CodePath.getState(codePath), true);
}
}
switch (node.type) {
case "Program":
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression": {
let codePath = analyzer.codePath;
// Mark the current path as the final node.
CodePath.getState(codePath).makeFinal();
// Emits onCodePathSegmentEnd event of the current segments.
leaveFromCurrentSegment(analyzer, node);
// Emits onCodePathEnd event of this code path.
debug.dump(`onCodePathEnd ${codePath.id}`);
analyzer.emitter.emit("onCodePathEnd", codePath, node);
debug.dumpDot(codePath);
codePath = analyzer.codePath = analyzer.codePath.upper;
if (codePath) {
debug.dumpState(node, CodePath.getState(codePath), true);
}
case "ArrowFunctionExpression":
case "StaticBlock": {
endCodePath();
break;
}
@ -662,6 +726,27 @@ function postprocess(analyzer, node) {
default:
break;
}
/*
* Special case: The right side of class field initializer is considered
* to be its own function, so we need to end a code path in this
* case.
*
* We need to check after the other checks in order to close the
* code paths in the correct order for code like this:
*
*
* class Foo {
* a = () => {}
* }
*
* In this case, The ArrowFunctionExpression code path is closed first
* and then we need to close the code path for the PropertyDefinition
* value.
*/
if (isPropertyDefinitionValue(node)) {
endCodePath();
}
}
//------------------------------------------------------------------------------
@ -674,7 +759,6 @@ function postprocess(analyzer, node) {
*/
class CodePathAnalyzer {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {EventGenerator} eventGenerator An event generator to wrap.
*/

View file

@ -33,7 +33,6 @@ function isReachable(segment) {
*/
class CodePathSegment {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {string} id An identifier.
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
@ -89,10 +88,10 @@ class CodePathSegment {
}
});
/* istanbul ignore if */
/* c8 ignore start */
if (debug.enabled) {
this.internal.nodes = [];
}
}/* c8 ignore stop */
}
/**
@ -101,7 +100,7 @@ class CodePathSegment {
* @returns {boolean} `true` if the segment is coming from the end of a loop.
*/
isLoopedPrevSegment(segment) {
return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
return this.internal.loopedPrevSegments.includes(segment);
}
/**

View file

@ -33,7 +33,7 @@ function addToReturnedOrThrown(dest, others, all, segments) {
const segment = segments[i];
dest.push(segment);
if (others.indexOf(segment) === -1) {
if (!others.includes(segment)) {
all.push(segment);
}
}
@ -59,7 +59,7 @@ function getContinueContext(state, label) {
context = context.upper;
}
/* istanbul ignore next: foolproof (syntax error) */
/* c8 ignore next */
return null;
}
@ -79,7 +79,7 @@ function getBreakContext(state, label) {
context = context.upper;
}
/* istanbul ignore next: foolproof (syntax error) */
/* c8 ignore next */
return null;
}
@ -219,7 +219,6 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
*/
class CodePathState {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {IdGenerator} idGenerator An id generator to generate id for code
* path segments.
@ -360,6 +359,7 @@ class CodePathState {
/**
* Pops the last choice context and finalizes it.
* @throws {Error} (Unreachable.)
* @returns {ChoiceContext} The popped context.
*/
popChoiceContext() {
@ -433,7 +433,7 @@ class CodePathState {
*/
return context;
/* istanbul ignore next */
/* c8 ignore next */
default:
throw new Error("unreachable");
}
@ -450,6 +450,7 @@ class CodePathState {
/**
* Makes a code path segment of the right-hand operand of a logical
* expression.
* @throws {Error} (Unreachable.)
* @returns {void}
*/
makeLogicalRight() {
@ -965,6 +966,7 @@ class CodePathState {
* `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
* and `ForStatement`.
* @param {string|null} label A label of the node which was triggered.
* @throws {Error} (Unreachable - unknown type.)
* @returns {void}
*/
pushLoopContext(type, label) {
@ -1028,7 +1030,7 @@ class CodePathState {
};
break;
/* istanbul ignore next */
/* c8 ignore next */
default:
throw new Error(`unknown type: "${type}"`);
}
@ -1036,6 +1038,7 @@ class CodePathState {
/**
* Pops the last context of a loop statement and finalizes it.
* @throws {Error} (Unreachable - unknown type.)
* @returns {void}
*/
popLoopContext() {
@ -1092,7 +1095,7 @@ class CodePathState {
);
break;
/* istanbul ignore next */
/* c8 ignore next */
default:
throw new Error("unreachable");
}
@ -1389,11 +1392,12 @@ class CodePathState {
const context = getBreakContext(this, label);
/* istanbul ignore else: foolproof (syntax error) */
if (context) {
context.brokenForkContext.add(forkContext.head);
}
/* c8 ignore next */
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
}
@ -1414,7 +1418,6 @@ class CodePathState {
const context = getContinueContext(this, label);
/* istanbul ignore else: foolproof (syntax error) */
if (context) {
if (context.continueDestSegments) {
makeLooped(this, forkContext.head, context.continueDestSegments);

View file

@ -21,13 +21,15 @@ const IdGenerator = require("./id-generator");
*/
class CodePath {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {string} id An identifier.
* @param {CodePath|null} upper The code path of the upper function scope.
* @param {Function} onLooped A callback function to notify looping.
* Creates a new instance.
* @param {Object} options Options for the function (see below).
* @param {string} options.id An identifier.
* @param {string} options.origin The type of code path origin.
* @param {CodePath|null} options.upper The code path of the upper function scope.
* @param {Function} options.onLooped A callback function to notify looping.
*/
constructor(id, upper, onLooped) {
constructor({ id, origin, upper, onLooped }) {
/**
* The identifier of this code path.
@ -36,6 +38,13 @@ class CodePath {
*/
this.id = id;
/**
* The reason that this code path was started. May be "program",
* "function", "class-field-initializer", or "class-static-block".
* @type {string}
*/
this.origin = origin;
/**
* The code path of the upper function scope.
* @type {CodePath|null}
@ -203,7 +212,7 @@ class CodePath {
}
// Reset the flag of skipping if all branches have been skipped.
if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) {
if (skippedSegment && segment.prevSegments.includes(skippedSegment)) {
skippedSegment = null;
}
visited[segment.id] = true;

View file

@ -20,8 +20,8 @@ const debug = require("debug")("eslint:code-path");
* @param {CodePathSegment} segment A segment to get.
* @returns {string} Id of the segment.
*/
/* istanbul ignore next */
function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
/* c8 ignore next */
function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring
return segment.id + (segment.reachable ? "" : "!");
}
@ -67,7 +67,7 @@ module.exports = {
* @param {boolean} leaving A flag whether or not it's leaving
* @returns {void}
*/
dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) {
dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) {
for (let i = 0; i < state.currentSegments.length; ++i) {
const segInternal = state.currentSegments[i].internal;
@ -98,7 +98,7 @@ module.exports = {
* @see http://www.graphviz.org
* @see http://www.webgraphviz.com
*/
dumpDot: !debug.enabled ? debug : /* istanbul ignore next */ function(codePath) {
dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) {
let text =
"\n" +
"digraph {\n" +
@ -115,7 +115,7 @@ module.exports = {
const traceMap = Object.create(null);
const arrows = this.makeDotArrows(codePath, traceMap);
for (const id in traceMap) { // eslint-disable-line guard-for-in
for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype
const segment = traceMap[id];
text += `${id}[`;

View file

@ -97,7 +97,6 @@ function mergeExtraSegments(context, segments) {
*/
class ForkContext {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {IdGenerator} idGenerator An identifier generator for segments.
* @param {ForkContext|null} upper An upper fork context.

View file

@ -18,7 +18,6 @@
*/
class IdGenerator {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {string} prefix Optional. A prefix of generated ids.
*/
@ -34,10 +33,10 @@ class IdGenerator {
next() {
this.n = 1 + this.n | 0;
/* istanbul ignore if */
/* c8 ignore start */
if (this.n < 0) {
this.n = 1;
}
}/* c8 ignore stop */
return this.prefix + this.n;
}

View file

@ -3,7 +3,7 @@
* @author Nicholas C. Zakas
*/
/* eslint-disable class-methods-use-this*/
/* eslint class-methods-use-this: off -- Methods desired on instance */
"use strict";
//------------------------------------------------------------------------------
@ -11,7 +11,11 @@
//------------------------------------------------------------------------------
const levn = require("levn"),
ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops");
{
Legacy: {
ConfigOps
}
} = require("@eslint/eslintrc/universal");
const debug = require("debug")("eslint:config-comment-parser");
@ -127,8 +131,7 @@ module.exports = class ConfigCommentParser {
const items = {};
// Collapse whitespace around commas
string.replace(/\s*,\s*/gu, ",").split(/,+/u).forEach(name => {
string.split(",").forEach(name => {
const trimmedName = name.trim();
if (trimmedName) {

File diff suppressed because it is too large Load diff

View file

@ -37,9 +37,7 @@ const esquery = require("esquery");
* @returns {any[]} The union of the input arrays
*/
function union(...arrays) {
// TODO(stephenwade): Replace this with arrays.flat() when we drop support for Node v10
return [...new Set([].concat(...arrays))];
return [...new Set(arrays.flat())];
}
/**
@ -100,6 +98,13 @@ function getPossibleTypes(parsedSelector) {
case "adjacent":
return getPossibleTypes(parsedSelector.right);
case "class":
if (parsedSelector.name === "function") {
return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"];
}
return null;
default:
return null;
@ -239,7 +244,6 @@ function parseSelector(rawSelector) {
*/
class NodeEventGenerator {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {SafeEmitter} emitter
* An SafeEmitter which is the destination of events. This emitter must already

View file

@ -32,18 +32,18 @@ const interpolate = require("./interpolate");
/**
* 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]
* @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions]
* @property {string} ruleId The rule ID
* @property {(0|1|2)} severity Severity of the error
* @property {(string|undefined)} message The message
* @property {(string|undefined)} [messageId] The message ID
* @property {number} line The line number
* @property {number} column The column number
* @property {(number|undefined)} [endLine] The ending line number
* @property {(number|undefined)} [endColumn] The ending column number
* @property {(string|null)} nodeType Type of node
* @property {string} source Source text
* @property {({text: string, range: (number[]|null)}|null)} [fix] The fix object
* @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] Suggestion info
*/
//------------------------------------------------------------------------------

View file

@ -30,6 +30,9 @@ function normalizeRule(rule) {
// Public Interface
//------------------------------------------------------------------------------
/**
* A storage for rules.
*/
class Rules {
constructor() {
this._rules = Object.create(null);

View file

@ -12,8 +12,8 @@
/**
* An event emitter
* @typedef {Object} SafeEmitter
* @property {function(eventName: string, listenerFunc: Function): void} on Adds a listener for a given event name
* @property {function(eventName: string, arg1?: any, arg2?: any, arg3?: any)} emit Emits an event with a given name.
* @property {(eventName: string, listenerFunc: Function) => void} on Adds a listener for a given event name
* @property {(eventName: string, arg1?: any, arg2?: any, arg3?: any) => void} emit Emits an event with a given name.
* This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments.
* @property {function(): string[]} eventNames Gets the list of event names that have registered listeners.
*/

View file

@ -80,8 +80,8 @@ SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
/**
* Try to use the 'fix' from a problem.
* @param {Message} problem The message object to apply fixes from
* @returns {boolean} Whether fix was successfully applied
* @param {Message} problem The message object to apply fixes from
* @returns {boolean} Whether fix was successfully applied
*/
function attemptFix(problem) {
const fix = problem.fix;

View file

@ -9,7 +9,7 @@
// Helpers
//------------------------------------------------------------------------------
/* istanbul ignore next */
/* c8 ignore next */
/**
* Align the string to left
* @param {string} str string to evaluate
@ -22,7 +22,7 @@ function alignLeft(str, len, ch) {
return str + new Array(len - str.length + 1).join(ch || " ");
}
/* istanbul ignore next */
/* c8 ignore next */
/**
* Align the string to right
* @param {string} str string to evaluate
@ -64,7 +64,7 @@ function getListSize() {
return TIMING_ENV_VAR_AS_INTEGER > 10 ? TIMING_ENV_VAR_AS_INTEGER : MINIMUM_SIZE;
}
/* istanbul ignore next */
/* c8 ignore next */
/**
* display the data
* @param {Object} data Data object to be displayed
@ -116,17 +116,17 @@ function display(data) {
return ALIGN[index](":", width + extraAlignment, "-");
}).join("|"));
console.log(table.join("\n")); // eslint-disable-line no-console
console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function
}
/* istanbul ignore next */
/* c8 ignore next */
module.exports = (function() {
const data = Object.create(null);
/**
* Time the run
* @param {*} key key from the data object
* @param {any} key key from the data object
* @param {Function} fn function to be called
* @returns {Function} function to be executed
* @private
@ -138,10 +138,11 @@ module.exports = (function() {
return function(...args) {
let t = process.hrtime();
const result = fn(...args);
fn(...args);
t = process.hrtime(t);
data[key] += t[0] * 1e3 + t[1] / 1e6;
return result;
};
}

536
node_modules/eslint/lib/options.js generated vendored
View file

@ -32,7 +32,7 @@ const optionator = require("optionator");
* @property {string[]} [ext] Specify JavaScript file extensions
* @property {boolean} fix Automatically fix problems
* @property {boolean} fixDryRun Automatically fix problems without saving the changes to the file system
* @property {("problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (problem, suggestion, layout)
* @property {("directive" | "problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (directive, problem, suggestion, layout)
* @property {string} format Use a specific output format
* @property {string[]} [global] Define global variables
* @property {boolean} [help] Show help
@ -50,7 +50,7 @@ const optionator = require("optionator");
* @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable directives
* @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default
* @property {Object} [rule] Specify rules
* @property {string[]} [rulesdir] Use additional rules from this directory
* @property {string[]} [rulesdir] Load additional rules from this directory. Deprecated: Use rules from plugins
* @property {boolean} stdin Lint code provided on <STDIN>
* @property {string} [stdinFilename] Specify filename to process STDIN as
* @property {boolean} quiet Report errors only
@ -63,261 +63,315 @@ const optionator = require("optionator");
//------------------------------------------------------------------------------
// exports "parse(args)", "generateHelp()", and "generateHelpForOption(optionName)"
module.exports = optionator({
prepend: "eslint [options] file.js [file.js] [dir]",
defaults: {
concatRepeatedArrays: true,
mergeRepeatedObjects: true
},
options: [
{
heading: "Basic configuration"
},
{
/**
* Creates the CLI options for ESLint.
* @param {boolean} usingFlatConfig Indicates if flat config is being used.
* @returns {Object} The optionator instance.
*/
module.exports = function(usingFlatConfig) {
let lookupFlag;
if (usingFlatConfig) {
lookupFlag = {
option: "config-lookup",
type: "Boolean",
default: "true",
description: "Disable look up for eslint.config.js"
};
} else {
lookupFlag = {
option: "eslintrc",
type: "Boolean",
default: "true",
description: "Disable use of configuration from .eslintrc.*"
},
{
option: "config",
alias: "c",
type: "path::String",
description: "Use this configuration, overriding .eslintrc.* config options if present"
},
{
};
}
let envFlag;
if (!usingFlatConfig) {
envFlag = {
option: "env",
type: "[String]",
description: "Specify environments"
},
{
};
}
let extFlag;
if (!usingFlatConfig) {
extFlag = {
option: "ext",
type: "[String]",
description: "Specify JavaScript file extensions"
},
{
option: "global",
type: "[String]",
description: "Define global variables"
},
{
option: "parser",
type: "String",
description: "Specify the parser to be used"
},
{
option: "parser-options",
type: "Object",
description: "Specify parser options"
},
{
};
}
let resolvePluginsFlag;
if (!usingFlatConfig) {
resolvePluginsFlag = {
option: "resolve-plugins-relative-to",
type: "path::String",
description: "A folder where plugins should be resolved from, CWD by default"
},
{
heading: "Specifying rules and plugins"
},
{
};
}
let rulesDirFlag;
if (!usingFlatConfig) {
rulesDirFlag = {
option: "rulesdir",
type: "[path::String]",
description: "Use additional rules from this directory"
},
{
option: "plugin",
type: "[String]",
description: "Specify plugins"
},
{
option: "rule",
type: "Object",
description: "Specify rules"
},
{
heading: "Fixing problems"
},
{
option: "fix",
type: "Boolean",
default: false,
description: "Automatically fix problems"
},
{
option: "fix-dry-run",
type: "Boolean",
default: false,
description: "Automatically fix problems without saving the changes to the file system"
},
{
option: "fix-type",
type: "Array",
description: "Specify the types of fixes to apply (problem, suggestion, layout)"
},
{
heading: "Ignoring files"
},
{
description: "Load additional rules from this directory. Deprecated: Use rules from plugins"
};
}
let ignorePathFlag;
if (!usingFlatConfig) {
ignorePathFlag = {
option: "ignore-path",
type: "path::String",
description: "Specify path of ignore file"
};
}
return optionator({
prepend: "eslint [options] file.js [file.js] [dir]",
defaults: {
concatRepeatedArrays: true,
mergeRepeatedObjects: true
},
{
option: "ignore",
type: "Boolean",
default: "true",
description: "Disable use of ignore files and patterns"
},
{
option: "ignore-pattern",
type: "[String]",
description: "Pattern of files to ignore (in addition to those in .eslintignore)",
concatRepeatedArrays: [true, {
oneValuePerFlag: true
}]
},
{
heading: "Using stdin"
},
{
option: "stdin",
type: "Boolean",
default: "false",
description: "Lint code provided on <STDIN>"
},
{
option: "stdin-filename",
type: "String",
description: "Specify filename to process STDIN as"
},
{
heading: "Handling warnings"
},
{
option: "quiet",
type: "Boolean",
default: "false",
description: "Report errors only"
},
{
option: "max-warnings",
type: "Int",
default: "-1",
description: "Number of warnings to trigger nonzero exit code"
},
{
heading: "Output"
},
{
option: "output-file",
alias: "o",
type: "path::String",
description: "Specify file to write report to"
},
{
option: "format",
alias: "f",
type: "String",
default: "stylish",
description: "Use a specific output format"
},
{
option: "color",
type: "Boolean",
alias: "no-color",
description: "Force enabling/disabling of color"
},
{
heading: "Inline configuration comments"
},
{
option: "inline-config",
type: "Boolean",
default: "true",
description: "Prevent comments from changing config or rules"
},
{
option: "report-unused-disable-directives",
type: "Boolean",
default: void 0,
description: "Adds reported errors for unused eslint-disable directives"
},
{
heading: "Caching"
},
{
option: "cache",
type: "Boolean",
default: "false",
description: "Only check changed files"
},
{
option: "cache-file",
type: "path::String",
default: ".eslintcache",
description: "Path to the cache file. Deprecated: use --cache-location"
},
{
option: "cache-location",
type: "path::String",
description: "Path to the cache file or directory"
},
{
option: "cache-strategy",
dependsOn: ["cache"],
type: "String",
default: "metadata",
enum: ["metadata", "content"],
description: "Strategy to use for detecting changed files in the cache"
},
{
heading: "Miscellaneous"
},
{
option: "init",
type: "Boolean",
default: "false",
description: "Run config initialization wizard"
},
{
option: "env-info",
type: "Boolean",
default: "false",
description: "Output execution environment information"
},
{
option: "error-on-unmatched-pattern",
type: "Boolean",
default: "true",
description: "Prevent errors when pattern is unmatched"
},
{
option: "exit-on-fatal-error",
type: "Boolean",
default: "false",
description: "Exit with exit code 2 in case of fatal error"
},
{
option: "debug",
type: "Boolean",
default: false,
description: "Output debugging information"
},
{
option: "help",
alias: "h",
type: "Boolean",
description: "Show help"
},
{
option: "version",
alias: "v",
type: "Boolean",
description: "Output the version number"
},
{
option: "print-config",
type: "path::String",
description: "Print the configuration for the given file"
}
]
});
options: [
{
heading: "Basic configuration"
},
lookupFlag,
{
option: "config",
alias: "c",
type: "path::String",
description: usingFlatConfig
? "Use this configuration instead of eslint.config.js"
: "Use this configuration, overriding .eslintrc.* config options if present"
},
envFlag,
extFlag,
{
option: "global",
type: "[String]",
description: "Define global variables"
},
{
option: "parser",
type: "String",
description: "Specify the parser to be used"
},
{
option: "parser-options",
type: "Object",
description: "Specify parser options"
},
resolvePluginsFlag,
{
heading: "Specify Rules and Plugins"
},
{
option: "plugin",
type: "[String]",
description: "Specify plugins"
},
{
option: "rule",
type: "Object",
description: "Specify rules"
},
rulesDirFlag,
{
heading: "Fix Problems"
},
{
option: "fix",
type: "Boolean",
default: false,
description: "Automatically fix problems"
},
{
option: "fix-dry-run",
type: "Boolean",
default: false,
description: "Automatically fix problems without saving the changes to the file system"
},
{
option: "fix-type",
type: "Array",
description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)"
},
{
heading: "Ignore Files"
},
ignorePathFlag,
{
option: "ignore",
type: "Boolean",
default: "true",
description: "Disable use of ignore files and patterns"
},
{
option: "ignore-pattern",
type: "[String]",
description: "Pattern of files to ignore (in addition to those in .eslintignore)",
concatRepeatedArrays: [true, {
oneValuePerFlag: true
}]
},
{
heading: "Use stdin"
},
{
option: "stdin",
type: "Boolean",
default: "false",
description: "Lint code provided on <STDIN>"
},
{
option: "stdin-filename",
type: "String",
description: "Specify filename to process STDIN as"
},
{
heading: "Handle Warnings"
},
{
option: "quiet",
type: "Boolean",
default: "false",
description: "Report errors only"
},
{
option: "max-warnings",
type: "Int",
default: "-1",
description: "Number of warnings to trigger nonzero exit code"
},
{
heading: "Output"
},
{
option: "output-file",
alias: "o",
type: "path::String",
description: "Specify file to write report to"
},
{
option: "format",
alias: "f",
type: "String",
default: "stylish",
description: "Use a specific output format"
},
{
option: "color",
type: "Boolean",
alias: "no-color",
description: "Force enabling/disabling of color"
},
{
heading: "Inline configuration comments"
},
{
option: "inline-config",
type: "Boolean",
default: "true",
description: "Prevent comments from changing config or rules"
},
{
option: "report-unused-disable-directives",
type: "Boolean",
default: void 0,
description: "Adds reported errors for unused eslint-disable directives"
},
{
heading: "Caching"
},
{
option: "cache",
type: "Boolean",
default: "false",
description: "Only check changed files"
},
{
option: "cache-file",
type: "path::String",
default: ".eslintcache",
description: "Path to the cache file. Deprecated: use --cache-location"
},
{
option: "cache-location",
type: "path::String",
description: "Path to the cache file or directory"
},
{
option: "cache-strategy",
dependsOn: ["cache"],
type: "String",
default: "metadata",
enum: ["metadata", "content"],
description: "Strategy to use for detecting changed files in the cache"
},
{
heading: "Miscellaneous"
},
{
option: "init",
type: "Boolean",
default: "false",
description: "Run config initialization wizard"
},
{
option: "env-info",
type: "Boolean",
default: "false",
description: "Output execution environment information"
},
{
option: "error-on-unmatched-pattern",
type: "Boolean",
default: "true",
description: "Prevent errors when pattern is unmatched"
},
{
option: "exit-on-fatal-error",
type: "Boolean",
default: "false",
description: "Exit with exit code 2 in case of fatal error"
},
{
option: "debug",
type: "Boolean",
default: false,
description: "Output debugging information"
},
{
option: "help",
alias: "h",
type: "Boolean",
description: "Show help"
},
{
option: "version",
alias: "v",
type: "Boolean",
description: "Output the version number"
},
{
option: "print-config",
type: "path::String",
description: "Print the configuration for the given file"
}
].filter(value => !!value)
});
};

1044
node_modules/eslint/lib/rule-tester/flat-rule-tester.js generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
*/
"use strict";
/* global describe, it */
/* globals describe, it -- Mocha globals */
/*
* This is a wrapper around mocha to allow for DRY unittests for eslint
@ -55,15 +55,19 @@ const ajv = require("../shared/ajv")({ strictDefaults: true });
const espreePath = require.resolve("espree");
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
const { SourceCode } = require("../source-code");
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/** @typedef {import("../shared/types").Parser} Parser */
/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
/**
* A test case that is expected to pass lint.
* @typedef {Object} ValidTestCase
* @property {string} [name] Name for the test case.
* @property {string} code Code for the test case.
* @property {any[]} [options] Options for the test case.
* @property {{ [name: string]: any }} [settings] Settings for the test case.
@ -78,6 +82,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
/**
* A test case that is expected to fail lint.
* @typedef {Object} InvalidTestCase
* @property {string} [name] Name for the test case.
* @property {string} code Code for the test case.
* @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
* @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
@ -103,6 +108,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
* @property {number} [endLine] The 1-based line number of the reported end location.
* @property {number} [endColumn] The 1-based column number of the reported end location.
*/
/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
//------------------------------------------------------------------------------
// Private Members
@ -120,6 +126,7 @@ let defaultConfig = { rules: {} };
* configuration
*/
const RuleTesterParameters = [
"name",
"code",
"filename",
"options",
@ -209,8 +216,11 @@ function freezeDeeply(x) {
* @returns {string} The sanitized text.
*/
function sanitize(text) {
if (typeof text !== "string") {
return "";
}
return text.replace(
/[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex
/[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
);
}
@ -284,6 +294,47 @@ function wrapParser(parser) {
};
}
/**
* Function to replace `SourceCode.prototype.getComments`.
* @returns {void}
* @throws {Error} Deprecation message.
*/
function getCommentsDeprecation() {
throw new Error(
"`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
);
}
/**
* Emit a deprecation warning if function-style format is being used.
* @param {string} ruleName Name of the rule.
* @returns {void}
*/
function emitLegacyRuleAPIWarning(ruleName) {
if (!emitLegacyRuleAPIWarning[`warned-${ruleName}`]) {
emitLegacyRuleAPIWarning[`warned-${ruleName}`] = true;
process.emitWarning(
`"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`,
"DeprecationWarning"
);
}
}
/**
* Emit a deprecation warning if rule has options but is missing the "meta.schema" property
* @param {string} ruleName Name of the rule.
* @returns {void}
*/
function emitMissingSchemaWarning(ruleName) {
if (!emitMissingSchemaWarning[`warned-${ruleName}`]) {
emitMissingSchemaWarning[`warned-${ruleName}`] = true;
process.emitWarning(
`"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`,
"DeprecationWarning"
);
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -298,6 +349,7 @@ const IT_ONLY = Symbol("itOnly");
* @this {Mocha}
* @param {string} text The description of the test case.
* @param {Function} method The logic of the test case.
* @throws {Error} Any error upon execution of `method`.
* @returns {any} Returned value of `method`.
*/
function itDefaultHandler(text, method) {
@ -322,6 +374,9 @@ function describeDefaultHandler(text, method) {
return method.call(this);
}
/**
* Mocha test wrapper.
*/
class RuleTester {
/**
@ -353,6 +408,7 @@ class RuleTester {
/**
* Set the configuration to use for all future tests
* @param {Object} config the configuration to use.
* @throws {TypeError} If non-object config.
* @returns {void}
*/
static setDefaultConfig(config) {
@ -437,7 +493,7 @@ class RuleTester {
if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") {
throw new Error(
"Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" +
"See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more."
"See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more."
);
}
if (typeof it === "function") {
@ -468,6 +524,8 @@ class RuleTester {
* valid: (ValidTestCase | string)[],
* invalid: InvalidTestCase[]
* }} test The collection of tests to run.
* @throws {TypeError|Error} If non-object `test`, or if a required
* scenario of the given type is missing.
* @returns {void}
*/
run(ruleName, rule, test) {
@ -493,6 +551,9 @@ class RuleTester {
].concat(scenarioErrors).join("\n"));
}
if (typeof rule === "function") {
emitLegacyRuleAPIWarning(ruleName);
}
linter.defineRule(ruleName, Object.assign({}, rule, {
@ -511,6 +572,7 @@ class RuleTester {
/**
* Run the rule for the given item
* @param {string|Object} item Item to run the rule against
* @throws {Error} If an invalid schema.
* @returns {Object} Eslint run result
* @private
*/
@ -549,6 +611,15 @@ class RuleTester {
if (hasOwnProperty(item, "options")) {
assert(Array.isArray(item.options), "options must be an array");
if (
item.options.length > 0 &&
typeof rule === "object" &&
(
!rule.meta || (rule.meta && (typeof rule.meta.schema === "undefined" || rule.meta.schema === null))
)
) {
emitMissingSchemaWarning(ruleName);
}
config.rules[ruleName] = [1].concat(item.options);
} else {
config.rules[ruleName] = 1;
@ -561,14 +632,18 @@ class RuleTester {
* The goal is to check whether or not AST was modified when
* running the rule under test.
*/
linter.defineRule("rule-tester/validate-ast", () => ({
Program(node) {
beforeAST = cloneDeeplyExcludesParent(node);
},
"Program:exit"(node) {
afterAST = node;
linter.defineRule("rule-tester/validate-ast", {
create() {
return {
Program(node) {
beforeAST = cloneDeeplyExcludesParent(node);
},
"Program:exit"(node) {
afterAST = node;
}
};
}
}));
});
if (typeof config.parser === "string") {
assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths");
@ -607,7 +682,16 @@ class RuleTester {
validate(config, "rule-tester", id => (id === ruleName ? rule : null));
// Verify the code.
const messages = linter.verify(code, config, filename);
const { getComments } = SourceCode.prototype;
let messages;
try {
SourceCode.prototype.getComments = getCommentsDeprecation;
messages = linter.verify(code, config, filename);
} finally {
SourceCode.prototype.getComments = getComments;
}
const fatalErrorMessage = messages.find(m => m.fatal);
assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
@ -656,6 +740,13 @@ class RuleTester {
* @private
*/
function testValidTemplate(item) {
const code = typeof item === "object" ? item.code : item;
assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
if (item.name) {
assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
}
const result = runRuleForItem(item);
const messages = result.messages;
@ -696,6 +787,10 @@ class RuleTester {
* @private
*/
function testInvalidTemplate(item) {
assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
if (item.name) {
assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
}
assert.ok(item.errors || item.errors === 0,
`Did not specify errors for an invalid test of ${ruleName}`);
@ -921,16 +1016,6 @@ class RuleTester {
);
}
// Rules that produce fixes must have `meta.fixable` property.
if (result.output !== item.code) {
assert.ok(
hasOwnProperty(rule, "meta"),
"Fixable rules should export a `meta.fixable` property."
);
// Linter throws if a rule that produced a fix has `meta` but doesn't have `meta.fixable`.
}
assertASTDidntChange(result.beforeAST, result.afterAST);
}
@ -938,11 +1023,11 @@ class RuleTester {
* This creates a mocha test suite and pipes all supplied info through
* one of the templates above.
*/
RuleTester.describe(ruleName, () => {
RuleTester.describe("valid", () => {
this.constructor.describe(ruleName, () => {
this.constructor.describe("valid", () => {
test.valid.forEach(valid => {
RuleTester[valid.only ? "itOnly" : "it"](
sanitize(typeof valid === "object" ? valid.code : valid),
this.constructor[valid.only ? "itOnly" : "it"](
sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
() => {
testValidTemplate(valid);
}
@ -950,10 +1035,10 @@ class RuleTester {
});
});
RuleTester.describe("invalid", () => {
this.constructor.describe("invalid", () => {
test.invalid.forEach(invalid => {
RuleTester[invalid.only ? "itOnly" : "it"](
sanitize(invalid.code),
this.constructor[invalid.only ? "itOnly" : "it"](
sanitize(invalid.name || invalid.code),
() => {
testInvalidTemplate(invalid);
}

View file

@ -134,13 +134,13 @@ function isPropertyDescriptor(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce getter and setter pairs in objects and classes",
category: "Best Practices",
description: "Enforce getter and setter pairs in objects and classes",
recommended: false,
url: "https://eslint.org/docs/rules/accessor-pairs"
},
@ -299,12 +299,12 @@ module.exports = {
* @private
*/
function checkPropertyDescriptor(node) {
const namesToCheck = node.properties
const namesToCheck = new Set(node.properties
.filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
.map(({ key }) => key.name);
.map(({ key }) => key.name));
const hasGetter = namesToCheck.includes("get");
const hasSetter = namesToCheck.includes("set");
const hasGetter = namesToCheck.has("get");
const hasSetter = namesToCheck.has("set");
if (checkSetWithoutGet && hasSetter && !hasGetter) {
report(node, "missingGetter");

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce linebreaks after opening and before closing array brackets",
category: "Stylistic Issues",
description: "Enforce linebreaks after opening and before closing array brackets",
recommended: false,
url: "https://eslint.org/docs/rules/array-bracket-newline"
},

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing inside array brackets",
category: "Stylistic Issues",
description: "Enforce consistent spacing inside array brackets",
recommended: false,
url: "https://eslint.org/docs/rules/array-bracket-spacing"
},

View file

@ -16,7 +16,7 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
/**
* Checks a given code path segment is reachable.
@ -125,7 +125,7 @@ function getArrayMethodName(node) {
}
}
/* istanbul ignore next: unreachable */
/* c8 ignore next */
return null;
}
@ -133,13 +133,13 @@ function getArrayMethodName(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce `return` statements in callbacks of array methods",
category: "Best Practices",
description: "Enforce `return` statements in callbacks of array methods",
recommended: false,
url: "https://eslint.org/docs/rules/array-callback-return"
},

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce line breaks after each array element",
category: "Stylistic Issues",
description: "Enforce line breaks after each array element",
recommended: false,
url: "https://eslint.org/docs/rules/array-element-newline"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require braces around arrow function bodies",
category: "ECMAScript 6",
description: "Require braces around arrow function bodies",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-body-style"
},

View file

@ -27,13 +27,13 @@ function hasBlockBody(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require parentheses around arrow function arguments",
category: "ECMAScript 6",
description: "Require parentheses around arrow function arguments",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-parens"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing before and after the arrow in arrow functions",
category: "ECMAScript 6",
description: "Enforce consistent spacing before and after the arrow in arrow functions",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-spacing"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce the use of variables within the scope they are defined",
category: "Best Practices",
description: "Enforce the use of variables within the scope they are defined",
recommended: false,
url: "https://eslint.org/docs/rules/block-scoped-var"
},
@ -113,6 +113,8 @@ module.exports = {
"SwitchStatement:exit": exitScope,
CatchClause: enterScope,
"CatchClause:exit": exitScope,
StaticBlock: enterScope,
"StaticBlock:exit": exitScope,
// Finds and reports references which are outside of valid scope.
VariableDeclaration: checkForVariables

View file

@ -11,13 +11,13 @@ const util = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "disallow or enforce spaces inside of blocks after opening block and before closing block",
category: "Stylistic Issues",
description: "Disallow or enforce spaces inside of blocks after opening block and before closing block",
recommended: false,
url: "https://eslint.org/docs/rules/block-spacing"
},
@ -41,7 +41,7 @@ module.exports = {
/**
* Gets the open brace token from a given node.
* @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get.
* @returns {Token} The token of the open brace.
*/
function getOpenBrace(node) {
@ -51,6 +51,12 @@ module.exports = {
}
return sourceCode.getLastToken(node, 1);
}
if (node.type === "StaticBlock") {
return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
}
// "BlockStatement"
return sourceCode.getFirstToken(node);
}
@ -73,8 +79,8 @@ module.exports = {
}
/**
* Reports invalid spacing style inside braces.
* @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
* Checks and reports invalid spacing style inside braces.
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check.
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
@ -158,6 +164,7 @@ module.exports = {
return {
BlockStatement: checkSpacingInsideBraces,
StaticBlock: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
}

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent brace style for blocks",
category: "Stylistic Issues",
description: "Enforce consistent brace style for blocks",
recommended: false,
url: "https://eslint.org/docs/rules/brace-style"
},
@ -156,6 +156,12 @@ module.exports = {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
}
},
StaticBlock(node) {
validateCurlyPair(
sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token
sourceCode.getLastToken(node)
);
},
ClassBody(node) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
},

View file

@ -1,6 +1,7 @@
/**
* @fileoverview Enforce return after a callback.
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
@ -8,6 +9,7 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -17,8 +19,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "require `return` statements after callbacks",
category: "Node.js and CommonJS",
description: "Require `return` statements after callbacks",
recommended: false,
url: "https://eslint.org/docs/rules/callback-return"
},
@ -52,7 +53,7 @@ module.exports = {
if (!node.parent) {
return null;
}
if (types.indexOf(node.parent.type) === -1) {
if (!types.includes(node.parent.type)) {
return findClosestParentOfType(node.parent, types);
}
return node.parent;
@ -86,7 +87,7 @@ module.exports = {
* @returns {boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return containsOnlyIdentifiers(node.callee) && callbacks.indexOf(sourceCode.getText(node.callee)) > -1;
return containsOnlyIdentifiers(node.callee) && callbacks.includes(sourceCode.getText(node.callee));
}
/**

View file

@ -5,17 +5,23 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce camelcase naming convention",
category: "Stylistic Issues",
description: "Enforce camelcase naming convention",
recommended: false,
url: "https://eslint.org/docs/rules/camelcase"
},
@ -55,32 +61,25 @@ module.exports = {
],
messages: {
notCamelCase: "Identifier '{{name}}' is not in camel case."
notCamelCase: "Identifier '{{name}}' is not in camel case.",
notCamelCasePrivate: "#{{name}} is not in camel case."
}
},
create(context) {
const options = context.options[0] || {};
let properties = options.properties || "";
const properties = options.properties === "never" ? "never" : "always";
const ignoreDestructuring = options.ignoreDestructuring;
const ignoreImports = options.ignoreImports;
const ignoreGlobals = options.ignoreGlobals;
const allow = options.allow || [];
let globalScope;
if (properties !== "always" && properties !== "never") {
properties = "always";
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = [];
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
const reported = new Set();
/**
* Checks if a string contains an underscore and isn't all upper-case
@ -89,9 +88,10 @@ module.exports = {
* @private
*/
function isUnderscored(name) {
const nameBody = name.replace(/^_+|_+$/gu, "");
// if there's an underscore, it might be A_CONSTANT, which is okay
return name.includes("_") && name !== name.toUpperCase();
return nameBody.includes("_") && nameBody !== nameBody.toUpperCase();
}
/**
@ -107,94 +107,76 @@ module.exports = {
}
/**
* Checks if a parent of a node is an ObjectPattern.
* @param {ASTNode} node The node to check.
* @returns {boolean} if the node is inside an ObjectPattern
* Checks if a given name is good or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is good.
* @private
*/
function isInsideObjectPattern(node) {
let current = node;
while (current) {
const parent = current.parent;
if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
return false;
}
if (current.type === "ObjectPattern") {
return true;
}
current = parent;
}
return false;
function isGoodName(name) {
return !isUnderscored(name) || isAllowed(name);
}
/**
* Checks whether the given node represents assignment target property in destructuring.
*
* For examples:
* ({a: b.foo} = c); // => true for `foo`
* ([a.foo] = b); // => true for `foo`
* ([a.foo = 1] = b); // => true for `foo`
* ({...a.foo} = b); // => true for `foo`
* @param {ASTNode} node An Identifier node to check
* @returns {boolean} True if the node is an assignment target property in destructuring.
* Checks if a given identifier reference or member expression is an assignment
* target.
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is an assignment target.
*/
function isAssignmentTargetPropertyInDestructuring(node) {
if (
node.parent.type === "MemberExpression" &&
node.parent.property === node &&
!node.parent.computed
) {
const effectiveParent = node.parent.parent;
return (
effectiveParent.type === "Property" &&
effectiveParent.value === node.parent &&
effectiveParent.parent.type === "ObjectPattern" ||
effectiveParent.type === "ArrayPattern" ||
effectiveParent.type === "RestElement" ||
(
effectiveParent.type === "AssignmentPattern" &&
effectiveParent.left === node.parent
)
);
}
return false;
}
/**
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a reference to a global variable.
*/
function isReferenceToGlobalVariable(node) {
const variable = globalScope.set.get(node.name);
return variable && variable.defs.length === 0 &&
variable.references.some(ref => ref.identifier === node);
}
/**
* Checks whether the given node represents a reference to a property of an object in an object literal expression.
* This allows to differentiate between a global variable that is allowed to be used as a reference, and the key
* of the expressed object (which shouldn't be allowed).
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a property name of an object literal expression
*/
function isPropertyNameInObjectLiteral(node) {
function isAssignmentTarget(node) {
const parent = node.parent;
return (
parent.type === "Property" &&
parent.parent.type === "ObjectExpression" &&
!parent.computed &&
parent.key === node
);
switch (parent.type) {
case "AssignmentExpression":
case "AssignmentPattern":
return parent.left === node;
case "Property":
return (
parent.parent.type === "ObjectPattern" &&
parent.value === node
);
case "ArrayPattern":
case "RestElement":
return true;
default:
return false;
}
}
/**
* Checks if a given binding identifier uses the original name as-is.
* - If it's in object destructuring or object expression, the original name is its property name.
* - If it's in import declaration, the original name is its exported name.
* @param {ASTNode} node The `Identifier` node to check.
* @returns {boolean} `true` if the identifier uses the original name as-is.
*/
function equalsToOriginalName(node) {
const localName = node.name;
const valueNode = node.parent.type === "AssignmentPattern"
? node.parent
: node;
const parent = valueNode.parent;
switch (parent.type) {
case "Property":
return (
(parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") &&
parent.value === valueNode &&
!parent.computed &&
parent.key.type === "Identifier" &&
parent.key.name === localName
);
case "ImportSpecifier":
return (
parent.local === node &&
astUtils.getModuleExportName(parent.imported) === localName
);
default:
return false;
}
}
/**
@ -204,122 +186,213 @@ module.exports = {
* @private
*/
function report(node) {
if (!reported.includes(node)) {
reported.push(node);
context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
if (reported.has(node.range[0])) {
return;
}
reported.add(node.range[0]);
// Report it.
context.report({
node,
messageId: node.type === "PrivateIdentifier"
? "notCamelCasePrivate"
: "notCamelCase",
data: { name: node.name }
});
}
/**
* Reports an identifier reference or a binding identifier.
* @param {ASTNode} node The `Identifier` node to report.
* @returns {void}
*/
function reportReferenceId(node) {
/*
* For backward compatibility, if it's in callings then ignore it.
* Not sure why it is.
*/
if (
node.parent.type === "CallExpression" ||
node.parent.type === "NewExpression"
) {
return;
}
/*
* For backward compatibility, if it's a default value of
* destructuring/parameters then ignore it.
* Not sure why it is.
*/
if (
node.parent.type === "AssignmentPattern" &&
node.parent.right === node
) {
return;
}
/*
* The `ignoreDestructuring` flag skips the identifiers that uses
* the property name as-is.
*/
if (ignoreDestructuring && equalsToOriginalName(node)) {
return;
}
report(node);
}
return {
// Report camelcase of global variable references ------------------
Program() {
globalScope = context.getScope();
const scope = context.getScope();
if (!ignoreGlobals) {
// Defined globals in config files or directive comments.
for (const variable of scope.variables) {
if (
variable.identifiers.length > 0 ||
isGoodName(variable.name)
) {
continue;
}
for (const reference of variable.references) {
/*
* For backward compatibility, this rule reports read-only
* references as well.
*/
reportReferenceId(reference.identifier);
}
}
}
// Undefined globals.
for (const reference of scope.through) {
const id = reference.identifier;
if (isGoodName(id.name)) {
continue;
}
/*
* For backward compatibility, this rule reports read-only
* references as well.
*/
reportReferenceId(id);
}
},
Identifier(node) {
// Report camelcase of declared variables --------------------------
[[
"VariableDeclaration",
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"ClassDeclaration",
"ClassExpression",
"CatchClause"
]](node) {
for (const variable of context.getDeclaredVariables(node)) {
if (isGoodName(variable.name)) {
continue;
}
const id = variable.identifiers[0];
/*
* Leading and trailing underscores are commonly used to flag
* private/protected identifiers, strip them before checking if underscored
*/
const name = node.name,
nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
// Report declaration.
if (!(ignoreDestructuring && equalsToOriginalName(id))) {
report(id);
}
// First, we ignore the node if it match the ignore list
if (isAllowed(name)) {
/*
* For backward compatibility, report references as well.
* It looks unnecessary because declarations are reported.
*/
for (const reference of variable.references) {
if (reference.init) {
continue; // Skip the write references of initializers.
}
reportReferenceId(reference.identifier);
}
}
},
// Report camelcase in properties ----------------------------------
[[
"ObjectExpression > Property[computed!=true] > Identifier.key",
"MethodDefinition[computed!=true] > Identifier.key",
"PropertyDefinition[computed!=true] > Identifier.key",
"MethodDefinition > PrivateIdentifier.key",
"PropertyDefinition > PrivateIdentifier.key"
]](node) {
if (properties === "never" || isGoodName(node.name)) {
return;
}
// Check if it's a global variable
if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
report(node);
},
"MemberExpression[computed!=true] > Identifier.property"(node) {
if (
properties === "never" ||
!isAssignmentTarget(node.parent) || // ← ignore read-only references.
isGoodName(node.name)
) {
return;
}
report(node);
},
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
// Report camelcase in import --------------------------------------
ImportDeclaration(node) {
for (const variable of context.getDeclaredVariables(node)) {
if (isGoodName(variable.name)) {
continue;
}
const id = variable.identifiers[0];
// "never" check properties
if (properties === "never") {
return;
// Report declaration.
if (!(ignoreImports && equalsToOriginalName(id))) {
report(id);
}
// Always report underscored object names
if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
report(node);
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
report(node);
} else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) {
report(node);
/*
* For backward compatibility, report references as well.
* It looks unnecessary because declarations are reported.
*/
for (const reference of variable.references) {
reportReferenceId(reference.identifier);
}
}
},
// Report camelcase in re-export -----------------------------------
[[
"ExportAllDeclaration > Identifier.exported",
"ExportSpecifier > Identifier.exported"
]](node) {
if (isGoodName(node.name)) {
return;
}
report(node);
},
// Report camelcase in labels --------------------------------------
[[
"LabeledStatement > Identifier.label",
/*
* Properties have their own rules, and
* AssignmentPattern nodes can be treated like Properties:
* e.g.: const { no_camelcased = false } = bar;
* For backward compatibility, report references as well.
* It looks unnecessary because declarations are reported.
*/
} else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
report(node);
}
const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
if (nameIsUnderscored && node.parent.computed) {
report(node);
}
// prevent checking righthand side of destructured object
if (node.parent.key === node && node.parent.value !== node) {
return;
}
const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
// ignore destructuring if the option is set, unless a new identifier is created
if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
report(node);
}
}
// "never" check properties or always ignore destructuring
if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
return;
}
// don't check right hand side of AssignmentExpression to prevent duplicate warnings
if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
report(node);
}
// Check if it's an import specifier
} else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
if (node.parent.type === "ImportSpecifier" && ignoreImports) {
return;
}
// Report only if the local imported identifier is underscored
if (
node.parent.local &&
node.parent.local.name === node.name &&
nameIsUnderscored
) {
report(node);
}
// Report anything that is underscored that isn't a CallExpression
} else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
report(node);
"BreakStatement > Identifier.label",
"ContinueStatement > Identifier.label"
]](node) {
if (isGoodName(node.name)) {
return;
}
report(node);
}
};
}
};

View file

@ -99,13 +99,13 @@ function createRegExpForIgnorePatterns(normalizedOptions) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce or disallow capitalization of the first letter of a comment",
category: "Stylistic Issues",
description: "Enforce or disallow capitalization of the first letter of a comment",
recommended: false,
url: "https://eslint.org/docs/rules/capitalized-comments"
},
@ -185,7 +185,7 @@ module.exports = {
return Boolean(
previousTokenOrComment &&
["Block", "Line"].indexOf(previousTokenOrComment.type) !== -1
["Block", "Line"].includes(previousTokenOrComment.type)
);
}

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce that class methods utilize `this`",
category: "Best Practices",
description: "Enforce that class methods utilize `this`",
recommended: false,
url: "https://eslint.org/docs/rules/class-methods-use-this"
},
@ -34,6 +34,10 @@ module.exports = {
items: {
type: "string"
}
},
enforceForClassFields: {
type: "boolean",
default: true
}
},
additionalProperties: false
@ -45,10 +49,27 @@ module.exports = {
},
create(context) {
const config = Object.assign({}, context.options[0]);
const enforceForClassFields = config.enforceForClassFields !== false;
const exceptMethods = new Set(config.exceptMethods || []);
const stack = [];
/**
* Push `this` used flag initialized with `false` onto the stack.
* @returns {void}
*/
function pushContext() {
stack.push(false);
}
/**
* Pop `this` used flag from the stack.
* @returns {boolean | undefined} `this` used flag
*/
function popContext() {
return stack.pop();
}
/**
* Initializes the current context to false and pushes it onto the stack.
* These booleans represent whether 'this' has been used in the context.
@ -56,7 +77,7 @@ module.exports = {
* @private
*/
function enterFunction() {
stack.push(false);
pushContext();
}
/**
@ -66,7 +87,14 @@ module.exports = {
* @private
*/
function isInstanceMethod(node) {
return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
switch (node.type) {
case "MethodDefinition":
return !node.static && node.kind !== "constructor";
case "PropertyDefinition":
return !node.static && enforceForClassFields;
default:
return false;
}
}
/**
@ -76,8 +104,19 @@ module.exports = {
* @private
*/
function isIncludedInstanceMethod(node) {
return isInstanceMethod(node) &&
(node.computed || !exceptMethods.has(node.key.name));
if (isInstanceMethod(node)) {
if (node.computed) {
return true;
}
const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : "";
const name = node.key.type === "Literal"
? astUtils.getStaticStringValue(node.key)
: (node.key.name || "");
return !exceptMethods.has(hashIfNeeded + name);
}
return false;
}
/**
@ -89,11 +128,12 @@ module.exports = {
* @private
*/
function exitFunction(node) {
const methodUsesThis = stack.pop();
const methodUsesThis = popContext();
if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
context.report({
node,
loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()),
messageId: "missingThis",
data: {
name: astUtils.getFunctionNameWithKind(node)
@ -118,8 +158,30 @@ module.exports = {
"FunctionDeclaration:exit": exitFunction,
FunctionExpression: enterFunction,
"FunctionExpression:exit": exitFunction,
/*
* Class field value are implicit functions.
*/
"PropertyDefinition > *.key:exit": pushContext,
"PropertyDefinition:exit": popContext,
/*
* Class static blocks are implicit functions. They aren't required to use `this`,
* but we have to push context so that it captures any use of `this` in the static block
* separately from enclosing contexts, because static blocks have their own `this` and it
* shouldn't count as used `this` in enclosing contexts.
*/
StaticBlock: pushContext,
"StaticBlock:exit": popContext,
ThisExpression: markThisUsed,
Super: markThisUsed
Super: markThisUsed,
...(
enforceForClassFields && {
"PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
"PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
}
)
};
}
};

View file

@ -50,7 +50,7 @@ function normalizeOptions(optionValue, ecmaVersion) {
objects: optionValue,
imports: optionValue,
exports: optionValue,
functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue
functions: ecmaVersion < 2017 ? "ignore" : optionValue
};
}
if (typeof optionValue === "object" && optionValue !== null) {
@ -70,13 +70,13 @@ function normalizeOptions(optionValue, ecmaVersion) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow trailing commas",
category: "Stylistic Issues",
description: "Require or disallow trailing commas",
recommended: false,
url: "https://eslint.org/docs/rules/comma-dangle"
},
@ -123,7 +123,8 @@ module.exports = {
}
]
}
]
],
additionalItems: false
},
messages: {
@ -133,7 +134,7 @@ module.exports = {
},
create(context) {
const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion);
const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion);
const sourceCode = context.getSourceCode();
@ -242,8 +243,18 @@ module.exports = {
node: lastItem,
loc: trailingToken.loc,
messageId: "unexpected",
fix(fixer) {
return fixer.remove(trailingToken);
*fix(fixer) {
yield fixer.remove(trailingToken);
/*
* Extend the range of the fix to include surrounding tokens to ensure
* that the element after which the comma is removed stays _last_.
* This intentionally makes conflicts in fix ranges with rules that may be
* adding or removing elements in the same autofix pass.
* https://github.com/eslint/eslint/issues/15660
*/
yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), "");
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
}
});
}
@ -281,8 +292,18 @@ module.exports = {
end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
},
messageId: "missing",
fix(fixer) {
return fixer.insertTextAfter(trailingToken, ",");
*fix(fixer) {
yield fixer.insertTextAfter(trailingToken, ",");
/*
* Extend the range of the fix to include surrounding tokens to ensure
* that the element after which the comma is inserted stays _last_.
* This intentionally makes conflicts in fix ranges with rules that may be
* adding or removing elements in the same autofix pass.
* https://github.com/eslint/eslint/issues/15660
*/
yield fixer.insertTextBefore(trailingToken, "");
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
}
});
}
@ -325,7 +346,7 @@ module.exports = {
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma,
ignore: () => {}
ignore() {}
};
return {

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing before and after commas",
category: "Stylistic Issues",
description: "Enforce consistent spacing before and after commas",
recommended: false,
url: "https://eslint.org/docs/rules/comma-spacing"
},
@ -103,38 +103,6 @@ module.exports = {
});
}
/**
* Validates the spacing around a comma token.
* @param {Object} tokens The tokens to be validated.
* @param {Token} tokens.comma The token representing the comma.
* @param {Token} [tokens.left] The last token before the comma.
* @param {Token} [tokens.right] The first token after the comma.
* @param {Token|ASTNode} reportItem The item to use when reporting an error.
* @returns {void}
* @private
*/
function validateCommaItemSpacing(tokens, reportItem) {
if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
(options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
) {
report(reportItem, "before", tokens.left);
}
if (tokens.right && astUtils.isClosingParenToken(tokens.right)) {
return;
}
if (tokens.right && !options.after && tokens.right.type === "Line") {
return;
}
if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
(options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
) {
report(reportItem, "after", tokens.right);
}
}
/**
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
* @param {ASTNode} node An ArrayExpression or ArrayPattern node.
@ -172,18 +140,44 @@ module.exports = {
return;
}
if (token && token.type === "JSXText") {
return;
}
const previousToken = tokensAndComments[i - 1];
const nextToken = tokensAndComments[i + 1];
validateCommaItemSpacing({
comma: token,
left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.includes(token) ? null : previousToken,
right: astUtils.isCommaToken(nextToken) ? null : nextToken
}, token);
if (
previousToken &&
!astUtils.isCommaToken(previousToken) && // ignore spacing between two commas
/*
* `commaTokensToIgnore` are ending commas of `null` elements (array holes/elisions).
* In addition to spacing between two commas, this can also ignore:
*
* - Spacing after `[` (controlled by array-bracket-spacing)
* Example: [ , ]
* ^
* - Spacing after a comment (for backwards compatibility, this was possibly unintentional)
* Example: [a, /* * / ,]
* ^
*/
!commaTokensToIgnore.includes(token) &&
astUtils.isTokenOnSameLine(previousToken, token) &&
options.before !== sourceCode.isSpaceBetweenTokens(previousToken, token)
) {
report(token, "before", previousToken);
}
if (
nextToken &&
!astUtils.isCommaToken(nextToken) && // ignore spacing between two commas
!astUtils.isClosingParenToken(nextToken) && // controlled by space-in-parens
!astUtils.isClosingBracketToken(nextToken) && // controlled by array-bracket-spacing
!astUtils.isClosingBraceToken(nextToken) && // controlled by object-curly-spacing
!(!options.after && nextToken.type === "Line") && // special case, allow space before line comment
astUtils.isTokenOnSameLine(token, nextToken) &&
options.after !== sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
report(token, "after", nextToken);
}
});
},
ArrayExpression: addNullElementsToIgnoreList,

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent comma style",
category: "Stylistic Issues",
description: "Enforce consistent comma style",
recommended: false,
url: "https://eslint.org/docs/rules/comma-style"
},

View file

@ -17,13 +17,13 @@ const { upperCaseFirst } = require("../shared/string-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum cyclomatic complexity allowed in a program",
category: "Best Practices",
description: "Enforce a maximum cyclomatic complexity allowed in a program",
recommended: false,
url: "https://eslint.org/docs/rules/complexity"
},
@ -75,60 +75,16 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
// Using a stack to store complexity (handling nested functions)
const fns = [];
// Using a stack to store complexity per code path
const complexities = [];
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
fns.push(1);
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
const complexity = fns.pop();
if (complexity > THRESHOLD) {
context.report({
node,
messageId: "complex",
data: { name, complexity, max: THRESHOLD }
});
}
}
/**
* Increase the complexity of the function in context
* Increase the complexity of the code path in context
* @returns {void}
* @private
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1]++;
}
}
/**
* Increase the switch complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node) {
// Avoiding `default`
if (node.test) {
increaseComplexity();
}
complexities[complexities.length - 1]++;
}
//--------------------------------------------------------------------------
@ -136,13 +92,14 @@ module.exports = {
//--------------------------------------------------------------------------
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
onCodePathStart() {
// The initial complexity is 1, representing one execution path in the CodePath
complexities.push(1);
},
// Each branching in the code adds 1 to the complexity
CatchClause: increaseComplexity,
ConditionalExpression: increaseComplexity,
LogicalExpression: increaseComplexity,
@ -150,14 +107,57 @@ module.exports = {
ForInStatement: increaseComplexity,
ForOfStatement: increaseComplexity,
IfStatement: increaseComplexity,
SwitchCase: increaseSwitchComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity,
// Avoid `default`
"SwitchCase[test]": increaseComplexity,
// Logical assignment operators have short-circuiting behavior
AssignmentExpression(node) {
if (astUtils.isLogicalAssignmentOperator(node.operator)) {
increaseComplexity();
}
},
onCodePathEnd(codePath, node) {
const complexity = complexities.pop();
/*
* This rule only evaluates complexity of functions, so "program" is excluded.
* Class field initializers and class static blocks are implicit functions. Therefore,
* they shouldn't contribute to the enclosing function's complexity, but their
* own complexity should be evaluated.
*/
if (
codePath.origin !== "function" &&
codePath.origin !== "class-field-initializer" &&
codePath.origin !== "class-static-block"
) {
return;
}
if (complexity > THRESHOLD) {
let name;
if (codePath.origin === "class-field-initializer") {
name = "class field initializer";
} else if (codePath.origin === "class-static-block") {
name = "class static block";
} else {
name = astUtils.getFunctionNameWithKind(node);
}
context.report({
node,
messageId: "complex",
data: {
name: upperCaseFirst(name),
complexity,
max: THRESHOLD
}
});
}
}
};

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing inside computed property brackets",
category: "Stylistic Issues",
description: "Enforce consistent spacing inside computed property brackets",
recommended: false,
url: "https://eslint.org/docs/rules/computed-property-spacing"
},
@ -195,7 +195,8 @@ module.exports = {
};
if (enforceForClassMembers) {
listeners.MethodDefinition = checkSpacing("key");
listeners.MethodDefinition =
listeners.PropertyDefinition = listeners.Property;
}
return listeners;

View file

@ -40,13 +40,13 @@ function isClassConstructor(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require `return` statements to either always or never specify values",
category: "Best Practices",
description: "Require `return` statements to either always or never specify values",
recommended: false,
url: "https://eslint.org/docs/rules/consistent-return"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce consistent naming when capturing the current execution context",
category: "Stylistic Issues",
description: "Enforce consistent naming when capturing the current execution context",
recommended: false,
url: "https://eslint.org/docs/rules/consistent-this"
},
@ -47,7 +47,7 @@ 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} name 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, name) {
@ -65,7 +65,7 @@ module.exports = {
function checkAssignment(node, name, value) {
const isThis = value.type === "ThisExpression";
if (aliases.indexOf(name) !== -1) {
if (aliases.includes(name)) {
if (!isThis || node.operator && node.operator !== "=") {
reportBadAssignment(node, name);
}

View file

@ -116,13 +116,13 @@ function isPossibleConstructor(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "require `super()` calls in constructors",
category: "ECMAScript 6",
description: "Require `super()` calls in constructors",
recommended: true,
url: "https://eslint.org/docs/rules/constructor-super"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce consistent brace style for all control statements",
category: "Best Practices",
description: "Enforce consistent brace style for all control statements",
recommended: false,
url: "https://eslint.org/docs/rules/curly"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce default clauses in switch statements to be last",
category: "Best Practices",
description: "Enforce default clauses in switch statements to be last",
recommended: false,
url: "https://eslint.org/docs/rules/default-case-last"
},

View file

@ -10,13 +10,13 @@ const DEFAULT_COMMENT_PATTERN = /^no default$/iu;
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require `default` cases in `switch` statements",
category: "Best Practices",
description: "Require `default` cases in `switch` statements",
recommended: false,
url: "https://eslint.org/docs/rules/default-case"
},
@ -50,8 +50,8 @@ module.exports = {
/**
* Shortcut to get last element of array
* @param {*[]} collection Array
* @returns {*} Last element
* @param {*[]} collection Array
* @returns {any} Last element
*/
function last(collection) {
return collection[collection.length - 1];

View file

@ -5,13 +5,13 @@
"use strict";
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce default parameters to be last",
category: "Best Practices",
description: "Enforce default parameters to be last",
recommended: false,
url: "https://eslint.org/docs/rules/default-param-last"
},
@ -25,8 +25,8 @@ module.exports = {
create(context) {
// eslint-disable-next-line jsdoc/require-description
/**
* Handler for function contexts.
* @param {ASTNode} node function node
* @returns {void}
*/

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent newlines before and after dots",
category: "Best Practices",
description: "Enforce consistent newlines before and after dots",
recommended: false,
url: "https://eslint.org/docs/rules/dot-location"
},

View file

@ -20,13 +20,13 @@ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
// `null` literal must be handled separately.
const literalTypesToCheck = new Set(["string", "boolean"]);
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce dot notation whenever possible",
category: "Best Practices",
description: "Enforce dot notation whenever possible",
recommended: false,
url: "https://eslint.org/docs/rules/dot-notation"
},
@ -76,7 +76,7 @@ module.exports = {
function checkComputedProperty(node, value) {
if (
validIdentifier.test(value) &&
(allowKeywords || keywords.indexOf(String(value)) === -1) &&
(allowKeywords || !keywords.includes(String(value))) &&
!(allowPattern && allowPattern.test(value))
) {
const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
@ -141,7 +141,8 @@ module.exports = {
if (
!allowKeywords &&
!node.computed &&
keywords.indexOf(String(node.property.name)) !== -1
node.property.type === "Identifier" &&
keywords.includes(String(node.property.name))
) {
context.report({
node: node.property,

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow newline at the end of files",
category: "Stylistic Issues",
description: "Require or disallow newline at the end of files",
recommended: false,
url: "https://eslint.org/docs/rules/eol-last"
},
@ -86,10 +86,15 @@ module.exports = {
});
} else if (mode === "never" && endsWithNewline) {
const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2];
// File is newline-terminated, but shouldn't be
context.report({
node,
loc: location,
loc: {
start: { line: sourceCode.lines.length - 1, column: secondLastLine.length },
end: { line: sourceCode.lines.length, column: 0 }
},
messageId: "unexpected",
fix(fixer) {
const finalEOLs = /(?:\r?\n)+$/u,

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require the use of `===` and `!==`",
category: "Best Practices",
description: "Require the use of `===` and `!==`",
recommended: false,
url: "https://eslint.org/docs/rules/eqeqeq"
},
@ -78,7 +78,7 @@ module.exports = {
/**
* Checks if an expression is a typeof expression
* @param {ASTNode} node The node to check
* @param {ASTNode} node The node to check
* @returns {boolean} if the node is a typeof expression
*/
function isTypeOf(node) {

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce \"for\" loop update clause moving the counter in the right direction.",
category: "Possible Errors",
description: "Enforce \"for\" loop update clause moving the counter in the right direction",
recommended: true,
url: "https://eslint.org/docs/rules/for-direction"
},

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow spacing between function identifiers and their invocations",
category: "Stylistic Issues",
description: "Require or disallow spacing between function identifiers and their invocations",
recommended: false,
url: "https://eslint.org/docs/rules/func-call-spacing"
},

View file

@ -44,7 +44,7 @@ function isModuleExports(pattern) {
* @returns {boolean} True if the string is a valid identifier
*/
function isIdentifier(name, ecmaVersion) {
if (ecmaVersion >= 6) {
if (ecmaVersion >= 2015) {
return esutils.keyword.isIdentifierES6(name);
}
return esutils.keyword.isIdentifierES5(name);
@ -68,13 +68,13 @@ const optionsObject = {
additionalProperties: false
};
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require function names to match the name of the variable or property to which they are assigned",
category: "Stylistic Issues",
description: "Require function names to match the name of the variable or property to which they are assigned",
recommended: false,
url: "https://eslint.org/docs/rules/func-name-matching"
},
@ -104,7 +104,7 @@ module.exports = {
const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always";
const considerPropertyDescriptor = options.considerPropertyDescriptor;
const includeModuleExports = options.includeCommonJSModuleExports;
const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
const ecmaVersion = context.languageOptions.ecmaVersion;
/**
* Check whether node is a certain CallExpression.
@ -196,21 +196,25 @@ module.exports = {
const isProp = node.left.type === "MemberExpression";
const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
if (node.right.id && name && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
report(node, name, node.right.id.name, isProp);
}
},
Property(node) {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
"Property, PropertyDefinition[value]"(node) {
if (!(node.value.type === "FunctionExpression" && node.value.id)) {
return;
}
if (node.key.type === "Identifier") {
if (node.key.type === "Identifier" && !node.computed) {
const functionName = node.value.id.name;
let propertyName = node.key.name;
if (considerPropertyDescriptor && propertyName === "value") {
if (
considerPropertyDescriptor &&
propertyName === "value" &&
node.parent.type === "ObjectExpression"
) {
if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", node.parent.parent)) {
const property = node.parent.parent.arguments[1];

View file

@ -24,13 +24,13 @@ function isFunctionName(variable) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require or disallow named `function` expressions",
category: "Stylistic Issues",
description: "Require or disallow named `function` expressions",
recommended: false,
url: "https://eslint.org/docs/rules/func-names"
},
@ -118,6 +118,7 @@ module.exports = {
return isObjectOrClassMethod(node) ||
(parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
(parent.type === "Property" && parent.value === node) ||
(parent.type === "PropertyDefinition" && parent.value === node) ||
(parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
(parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node);
}

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce the consistent use of either `function` declarations or expressions",
category: "Stylistic Issues",
description: "Enforce the consistent use of either `function` declarations or expressions",
recommended: false,
url: "https://eslint.org/docs/rules/func-style"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce line breaks between arguments of a function call",
category: "Stylistic Issues",
description: "Enforce line breaks between arguments of a function call",
recommended: false,
url: "https://eslint.org/docs/rules/function-call-argument-newline"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent line breaks inside function parentheses",
category: "Stylistic Issues",
description: "Enforce consistent line breaks inside function parentheses",
recommended: false,
url: "https://eslint.org/docs/rules/function-paren-newline"
},
@ -183,6 +183,7 @@ module.exports = {
/**
* Gets the left paren and right paren tokens of a node.
* @param {ASTNode} node The node with parens
* @throws {TypeError} Unexpected node type.
* @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
* Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
* with a single parameter)
@ -190,10 +191,13 @@ module.exports = {
function getParenTokens(node) {
switch (node.type) {
case "NewExpression":
if (!node.arguments.length && !(
astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
astUtils.isClosingParenToken(sourceCode.getLastToken(node))
)) {
if (!node.arguments.length &&
!(
astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
node.callee.range[1] < node.range[1]
)
) {
// If the NewExpression does not have parens (e.g. `new Foo`), return null.
return null;
@ -226,9 +230,13 @@ module.exports = {
return null;
}
const rightParen = node.params.length
? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
: sourceCode.getTokenAfter(firstToken);
return {
leftParen: firstToken,
rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken)
rightParen
};
}

View file

@ -25,13 +25,13 @@ const OVERRIDE_SCHEMA = {
]
};
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing around `*` operators in generator functions",
category: "ECMAScript 6",
description: "Enforce consistent spacing around `*` operators in generator functions",
recommended: false,
url: "https://eslint.org/docs/rules/generator-star-spacing"
},

View file

@ -29,13 +29,13 @@ function isReachable(segment) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce `return` statements in getters",
category: "Possible Errors",
description: "Enforce `return` statements in getters",
recommended: true,
url: "https://eslint.org/docs/rules/getter-return"
},
@ -112,18 +112,24 @@ module.exports = {
}
if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
// Object.defineProperty()
if (parent.parent.parent.type === "CallExpression" &&
astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
return true;
// Object.defineProperty() or Reflect.defineProperty()
if (parent.parent.parent.type === "CallExpression") {
const callNode = parent.parent.parent.callee;
if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") ||
astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) {
return true;
}
}
// Object.defineProperties()
// Object.defineProperties() or Object.create()
if (parent.parent.parent.type === "Property" &&
parent.parent.parent.parent.type === "ObjectExpression" &&
parent.parent.parent.parent.parent.type === "CallExpression" &&
astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
return true;
parent.parent.parent.parent.parent.type === "CallExpression") {
const callNode = parent.parent.parent.parent.parent.callee;
return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") ||
astUtils.isSpecificMemberAccess(callNode, "Object", "create");
}
}
}

View file

@ -1,11 +1,12 @@
/**
* @fileoverview Rule for disallowing require() outside of the top-level module context
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
const ACCEPTABLE_PARENTS = [
const ACCEPTABLE_PARENTS = new Set([
"AssignmentExpression",
"VariableDeclarator",
"MemberExpression",
@ -15,7 +16,7 @@ const ACCEPTABLE_PARENTS = [
"Program",
"VariableDeclaration",
"ChainExpression"
];
]);
/**
* Finds the eslint-scope reference in the given scope.
@ -27,10 +28,11 @@ function findReference(scope, node) {
const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1]);
/* istanbul ignore else: correctly returns null */
if (references.length === 1) {
return references[0];
}
/* c8 ignore next */
return null;
}
@ -47,6 +49,7 @@ function isShadowed(scope, node) {
return reference && reference.resolved && reference.resolved.defs.length > 0;
}
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -56,8 +59,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "require `require()` calls to be placed at top-level module scope",
category: "Node.js and CommonJS",
description: "Require `require()` calls to be placed at top-level module scope",
recommended: false,
url: "https://eslint.org/docs/rules/global-require"
},
@ -74,7 +76,7 @@ module.exports = {
const currentScope = context.getScope();
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.indexOf(parent.type) > -1);
const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type));
if (!isGoodRequire) {
context.report({ node, messageId: "unexpected" });

View file

@ -90,13 +90,13 @@ function isAccessorKind(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require grouped accessor pairs in object literals and classes",
category: "Best Practices",
description: "Require grouped accessor pairs in object literals and classes",
recommended: false,
url: "https://eslint.org/docs/rules/grouped-accessor-pairs"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require `for-in` loops to include an `if` statement",
category: "Best Practices",
description: "Require `for-in` loops to include an `if` statement",
recommended: false,
url: "https://eslint.org/docs/rules/guard-for-in"
},

View file

@ -1,6 +1,7 @@
/**
* @fileoverview Ensure handling of errors when we know they exist.
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
@ -9,6 +10,7 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -18,8 +20,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "require error handling in callbacks",
category: "Node.js and CommonJS",
description: "Require error handling in callbacks",
recommended: false,
url: "https://eslint.org/docs/rules/handle-callback-err"
},

View file

@ -2,6 +2,7 @@
* @fileoverview Rule that warns when identifier names that are
* specified in the configuration are used.
* @author Keith Cirkel (http://keithcirkel.co.uk)
* @deprecated in ESLint v7.5.0
*/
"use strict";
@ -109,6 +110,7 @@ function isShorthandPropertyDefinition(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -117,8 +119,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "disallow specified identifiers",
category: "Stylistic Issues",
description: "Disallow specified identifiers",
recommended: false,
url: "https://eslint.org/docs/rules/id-blacklist"
},
@ -205,7 +206,17 @@ module.exports = {
* @private
*/
function report(node) {
if (!reportedNodes.has(node)) {
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (!reportedNodes.has(node.range.toString())) {
context.report({
node,
messageId: "restricted",
@ -213,8 +224,9 @@ module.exports = {
name: node.name
}
});
reportedNodes.add(node);
reportedNodes.add(node.range.toString());
}
}
return {

View file

@ -69,14 +69,14 @@ function isRenamedImport(node) {
}
/**
* Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
* Checks whether the given node is an ObjectPattern destructuring.
*
* Examples:
* const { a : b } = foo; // node `a` is renamed node.
* const { a : b } = foo;
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
* @returns {boolean} `true` if the node is in an ObjectPattern destructuring.
*/
function isRenamedInDestructuring(node) {
function isPropertyNameInDestructuring(node) {
const parent = node.parent;
return (
@ -84,38 +84,22 @@ function isRenamedInDestructuring(node) {
!parent.computed &&
parent.type === "Property" &&
parent.parent.type === "ObjectPattern" &&
parent.value !== node &&
parent.key === node
)
);
}
/**
* Checks whether the given node represents shorthand definition of a property in an object literal.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a shorthand property definition.
*/
function isShorthandPropertyDefinition(node) {
const parent = node.parent;
return (
parent.type === "Property" &&
parent.parent.type === "ObjectExpression" &&
parent.shorthand
);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow specified identifiers",
category: "Stylistic Issues",
description: "Disallow specified identifiers",
recommended: false,
url: "https://eslint.org/docs/rules/id-denylist"
},
@ -128,7 +112,8 @@ module.exports = {
uniqueItems: true
},
messages: {
restricted: "Identifier '{{name}}' is restricted."
restricted: "Identifier '{{name}}' is restricted.",
restrictedPrivate: "Identifier '#{{name}}' is restricted."
}
},
@ -187,11 +172,8 @@ module.exports = {
parent.type !== "CallExpression" &&
parent.type !== "NewExpression" &&
!isRenamedImport(node) &&
!isRenamedInDestructuring(node) &&
!(
isReferenceToGlobalVariable(node) &&
!isShorthandPropertyDefinition(node)
)
!isPropertyNameInDestructuring(node) &&
!isReferenceToGlobalVariable(node)
);
}
@ -202,15 +184,27 @@ module.exports = {
* @private
*/
function report(node) {
if (!reportedNodes.has(node)) {
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (!reportedNodes.has(node.range.toString())) {
const isPrivate = node.type === "PrivateIdentifier";
context.report({
node,
messageId: "restricted",
messageId: isPrivate ? "restrictedPrivate" : "restricted",
data: {
name: node.name
}
});
reportedNodes.add(node);
reportedNodes.add(node.range.toString());
}
}
@ -220,7 +214,10 @@ module.exports = {
globalScope = context.getScope();
},
Identifier(node) {
[[
"Identifier",
"PrivateIdentifier"
]](node) {
if (isRestricted(node.name) && shouldCheck(node)) {
report(node);
}

View file

@ -6,17 +6,56 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const GraphemeSplitter = require("grapheme-splitter");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks if the string given as argument is ASCII or not.
* @param {string} value A string that you want to know if it is ASCII or not.
* @returns {boolean} `true` if `value` is ASCII string.
*/
function isASCII(value) {
if (typeof value !== "string") {
return false;
}
return /^[\u0020-\u007f]*$/u.test(value);
}
/** @type {GraphemeSplitter | undefined} */
let splitter;
/**
* Gets the length of the string. If the string is not in ASCII, counts graphemes.
* @param {string} value A string that you want to get the length.
* @returns {number} The length of `value`.
*/
function getStringLength(value) {
if (isASCII(value)) {
return value.length;
}
if (!splitter) {
splitter = new GraphemeSplitter();
}
return splitter.countGraphemes(value);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce minimum and maximum identifier lengths",
category: "Stylistic Issues",
description: "Enforce minimum and maximum identifier lengths",
recommended: false,
url: "https://eslint.org/docs/rules/id-length"
},
@ -55,7 +94,9 @@ module.exports = {
],
messages: {
tooShort: "Identifier name '{{name}}' is too short (< {{min}}).",
tooLong: "Identifier name '{{name}}' is too long (> {{max}})."
tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).",
tooLong: "Identifier name '{{name}}' is too long (> {{max}}).",
tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})."
}
},
@ -66,7 +107,7 @@ module.exports = {
const properties = options.properties !== "never";
const exceptions = new Set(options.exceptions);
const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));
const reportedNode = new Set();
const reportedNodes = new Set();
/**
* Checks if a string matches the provided exception patterns
@ -99,12 +140,14 @@ module.exports = {
Property(parent, node) {
if (parent.parent.type === "ObjectPattern") {
const isKeyAndValueSame = parent.value.name === parent.key.name;
return (
parent.value !== parent.key && parent.value === node ||
parent.value === parent.key && parent.key === node && properties
!isKeyAndValueSame && parent.value === node ||
isKeyAndValueSame && parent.key === node && properties
);
}
return properties && !parent.computed && parent.key === node;
return properties && !parent.computed && parent.key.name === node.name;
},
ImportDefaultSpecifier: true,
RestElement: true,
@ -113,17 +156,23 @@ module.exports = {
ClassDeclaration: true,
FunctionDeclaration: true,
MethodDefinition: true,
PropertyDefinition: true,
CatchClause: true,
ArrayPattern: true
};
return {
Identifier(node) {
[[
"Identifier",
"PrivateIdentifier"
]](node) {
const name = node.name;
const parent = node.parent;
const isShort = name.length < minLength;
const isLong = name.length > maxLength;
const nameLength = getStringLength(name);
const isShort = nameLength < minLength;
const isLong = nameLength > maxLength;
if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
return; // Nothing to report
@ -131,11 +180,27 @@ module.exports = {
const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) {
reportedNode.add(node);
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) {
reportedNodes.add(node.range.toString());
let messageId = isShort ? "tooShort" : "tooLong";
if (node.type === "PrivateIdentifier") {
messageId += "Private";
}
context.report({
node,
messageId: isShort ? "tooShort" : "tooLong",
messageId,
data: { name, min: minLength, max: maxLength }
});
}

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require identifiers to match a specified regular expression",
category: "Stylistic Issues",
description: "Require identifiers to match a specified regular expression",
recommended: false,
url: "https://eslint.org/docs/rules/id-match"
},
@ -31,6 +31,10 @@ module.exports = {
type: "boolean",
default: false
},
classFields: {
type: "boolean",
default: false
},
onlyDeclarations: {
type: "boolean",
default: false
@ -44,7 +48,8 @@ module.exports = {
}
],
messages: {
notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'."
notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.",
notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'."
}
},
@ -57,20 +62,36 @@ module.exports = {
regexp = new RegExp(pattern, "u");
const options = context.options[1] || {},
properties = !!options.properties,
checkProperties = !!options.properties,
checkClassFields = !!options.classFields,
onlyDeclarations = !!options.onlyDeclarations,
ignoreDestructuring = !!options.ignoreDestructuring;
let globalScope;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = new Map();
const reportedNodes = new Set();
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]);
const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]);
/**
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a reference to a global variable.
*/
function isReferenceToGlobalVariable(node) {
const variable = globalScope.set.get(node.name);
return variable && variable.defs.length === 0 &&
variable.references.some(ref => ref.identifier === node);
}
/**
* Checks if a string matches the provided pattern
* @param {string} name The string to check.
@ -120,29 +141,51 @@ module.exports = {
* @private
*/
function report(node) {
if (!reported.has(node)) {
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (!reportedNodes.has(node.range.toString())) {
const messageId = (node.type === "PrivateIdentifier")
? "notMatchPrivate" : "notMatch";
context.report({
node,
messageId: "notMatch",
messageId,
data: {
name: node.name,
pattern
}
});
reported.set(node, true);
reportedNodes.add(node.range.toString());
}
}
return {
Program() {
globalScope = context.getScope();
},
Identifier(node) {
const name = node.name,
parent = node.parent,
effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
if (isReferenceToGlobalVariable(node)) {
return;
}
if (parent.type === "MemberExpression") {
if (!properties) {
if (!checkProperties) {
return;
}
@ -168,6 +211,17 @@ module.exports = {
}
}
// For https://github.com/eslint/eslint/issues/15123
} else if (
parent.type === "Property" &&
parent.parent.type === "ObjectExpression" &&
parent.key === node &&
!parent.computed
) {
if (checkProperties && isInvalid(name)) {
report(node);
}
/*
* Properties have their own rules, and
* AssignmentPattern nodes can be treated like Properties:
@ -176,8 +230,7 @@ module.exports = {
} else if (parent.type === "Property" || parent.type === "AssignmentPattern") {
if (parent.parent && parent.parent.type === "ObjectPattern") {
if (parent.shorthand && parent.value.left && isInvalid(name)) {
if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) {
report(node);
}
@ -197,7 +250,7 @@ module.exports = {
}
// never check properties or always ignore destructuring
if (!properties || (ignoreDestructuring && isInsideObjectPattern(node))) {
if ((!checkProperties && !parent.computed) || (ignoreDestructuring && isInsideObjectPattern(node))) {
return;
}
@ -214,10 +267,29 @@ module.exports = {
report(node);
}
} else if (parent.type === "PropertyDefinition") {
if (checkClassFields && isInvalid(name)) {
report(node);
}
// Report anything that is invalid that isn't a CallExpression
} else if (shouldReport(effectiveParent, name)) {
report(node);
}
},
"PrivateIdentifier"(node) {
const isClassField = node.parent.type === "PropertyDefinition";
if (isClassField && !checkClassFields) {
return;
}
if (isInvalid(node.name)) {
report(node);
}
}
};

View file

@ -9,13 +9,13 @@ const { isCommentToken, isNotOpeningParenToken } = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce the location of arrow function bodies",
category: "Stylistic Issues",
description: "Enforce the location of arrow function bodies",
recommended: false,
url: "https://eslint.org/docs/rules/implicit-arrow-linebreak"
},

View file

@ -4,6 +4,7 @@
* This rule has been ported and modified from nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
* @deprecated in ESLint v4.0.0
*/
"use strict";
@ -17,15 +18,15 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/* istanbul ignore next: this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway. */
// this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway.
/* c8 ignore next */
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent indentation",
category: "Stylistic Issues",
description: "Enforce consistent indentation",
recommended: false,
url: "https://eslint.org/docs/rules/indent-legacy"
},
@ -211,10 +212,10 @@ module.exports = {
if (context.options[0] === "tab") {
indentSize = 1;
indentType = "tab";
} else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") {
} else /* c8 ignore start */ if (typeof context.options[0] === "number") {
indentSize = context.options[0];
indentType = "space";
}
}/* c8 ignore stop */
if (context.options[1]) {
const opts = context.options[1];
@ -752,7 +753,7 @@ module.exports = {
if (typeof options.CallExpression.arguments === "number") {
nodeIndent += options.CallExpression.arguments * indentSize;
} else if (options.CallExpression.arguments === "first") {
if (parent.arguments.indexOf(node) !== -1) {
if (parent.arguments.includes(node)) {
nodeIndent = parent.arguments[0].loc.start.column;
}
} else {
@ -839,7 +840,7 @@ module.exports = {
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
if (node.parent && statementsWithProperties.includes(node.parent.type) && isNodeBodyBlock(node)) {
indent = getNodeIndent(node.parent).goodChar;
} else if (node.parent && node.parent.type === "CatchClause") {
indent = getNodeIndent(node.parent.parent).goodChar;

View file

@ -12,7 +12,7 @@
// Requirements
//------------------------------------------------------------------------------
const createTree = require("functional-red-black-tree");
const { OrderedMap } = require("js-sdsl");
const astUtils = require("./utils/ast-utils");
@ -60,12 +60,15 @@ const KNOWN_NODES = new Set([
"NewExpression",
"ObjectExpression",
"ObjectPattern",
"PrivateIdentifier",
"Program",
"Property",
"PropertyDefinition",
"RestElement",
"ReturnStatement",
"SequenceExpression",
"SpreadElement",
"StaticBlock",
"Super",
"SwitchCase",
"SwitchStatement",
@ -132,23 +135,18 @@ class BinarySearchTree {
* Creates an empty tree
*/
constructor() {
this._rbTree = createTree();
this._orderedMap = new OrderedMap();
this._orderedMapEnd = this._orderedMap.end();
}
/**
* Inserts an entry into the tree.
* @param {number} key The entry's key
* @param {*} value The entry's value
* @param {any} value The entry's value
* @returns {void}
*/
insert(key, value) {
const iterator = this._rbTree.find(key);
if (iterator.valid) {
this._rbTree = iterator.update(value);
} else {
this._rbTree = this._rbTree.insert(key, value);
}
this._orderedMap.setElement(key, value);
}
/**
@ -157,9 +155,13 @@ class BinarySearchTree {
* @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists.
*/
findLe(key) {
const iterator = this._rbTree.le(key);
const iterator = this._orderedMap.reverseLowerBound(key);
return iterator && { key: iterator.key, value: iterator.value };
if (iterator.equals(this._orderedMapEnd)) {
return {};
}
return { key: iterator.pointer[0], value: iterator.pointer[1] };
}
/**
@ -174,11 +176,20 @@ class BinarySearchTree {
if (start === end) {
return;
}
const iterator = this._rbTree.ge(start);
const iterator = this._orderedMap.lowerBound(start);
while (iterator.valid && iterator.key < end) {
this._rbTree = this._rbTree.remove(iterator.key);
iterator.next();
if (iterator.equals(this._orderedMapEnd)) {
return;
}
if (end > this._orderedMap.back()[0]) {
while (!iterator.equals(this._orderedMapEnd)) {
this._orderedMap.eraseElementByIterator(iterator);
}
} else {
while (iterator.pointer[0] < end) {
this._orderedMap.eraseElementByIterator(iterator);
}
}
}
}
@ -188,7 +199,6 @@ class BinarySearchTree {
*/
class TokenInfo {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {SourceCode} sourceCode A SourceCode object
*/
@ -238,7 +248,6 @@ class TokenInfo {
*/
class OffsetStorage {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {TokenInfo} tokenInfo a TokenInfo instance
* @param {number} indentSize The desired size of each indentation level
@ -263,7 +272,7 @@ class OffsetStorage {
/**
* Sets the offset column of token B to match the offset column of token A.
* **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
* - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
* most cases, `setDesiredOffset` should be used instead.
* @param {Token} baseToken The first token
* @param {Token} offsetToken The second token, whose offset should be matched to the first token
@ -352,11 +361,11 @@ class OffsetStorage {
* Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
* list could represent the state of the offset tree at a given point:
*
* * Tokens starting in the interval [0, 15) are aligned with the beginning of the file
* * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
* * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
* * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
* * Tokens starting in the interval [820, ) are offset by 1 indent level from the `baz` token
* - Tokens starting in the interval [0, 15) are aligned with the beginning of the file
* - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
* - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
* - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
* - Tokens starting in the interval [820, ) are offset by 1 indent level from the `baz` token
*
* The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
* `setDesiredOffsets([30, 43], fooToken, 1);`
@ -493,13 +502,13 @@ const ELEMENT_LIST_SCHEMA = {
]
};
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent indentation",
category: "Stylistic Issues",
description: "Enforce consistent indentation",
recommended: false,
url: "https://eslint.org/docs/rules/indent"
},
@ -584,6 +593,16 @@ module.exports = {
},
additionalProperties: false
},
StaticBlock: {
type: "object",
properties: {
body: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
},
CallExpression: {
type: "object",
properties: {
@ -647,6 +666,9 @@ module.exports = {
parameters: DEFAULT_PARAMETER_INDENT,
body: DEFAULT_FUNCTION_BODY_INDENT
},
StaticBlock: {
body: DEFAULT_FUNCTION_BODY_INDENT
},
CallExpression: {
arguments: DEFAULT_PARAMETER_INDENT
},
@ -782,7 +804,7 @@ module.exports = {
let statement = node.parent && node.parent.parent;
while (
statement.type === "UnaryExpression" && ["!", "~", "+", "-"].indexOf(statement.operator) > -1 ||
statement.type === "UnaryExpression" && ["!", "~", "+", "-"].includes(statement.operator) ||
statement.type === "AssignmentExpression" ||
statement.type === "LogicalExpression" ||
statement.type === "SequenceExpression" ||
@ -902,18 +924,6 @@ module.exports = {
}
offsets.setDesiredOffsets([firstBodyToken.range[0], lastBodyToken.range[1]], lastParentToken, 1);
/*
* For blockless nodes with semicolon-first style, don't indent the semicolon.
* e.g.
* if (foo) bar()
* ; [1, 2, 3].map(foo)
*/
const lastToken = sourceCode.getLastToken(node);
if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) {
offsets.setDesiredOffset(lastToken, lastParentToken, 0);
}
}
}
@ -1209,7 +1219,7 @@ module.exports = {
}
},
"DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement": node => addBlocklessNodeIndent(node.body),
"DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement, WithStatement": node => addBlocklessNodeIndent(node.body),
ExportNamedDeclaration(node) {
if (node.declaration === null) {
@ -1257,6 +1267,50 @@ module.exports = {
}
},
/*
* For blockless nodes with semicolon-first style, don't indent the semicolon.
* e.g.
* if (foo)
* bar()
* ; [1, 2, 3].map(foo)
*
* Traversal into the node sets indentation of the semicolon, so we need to override it on exit.
*/
":matches(DoWhileStatement, ForStatement, ForInStatement, ForOfStatement, IfStatement, WhileStatement, WithStatement):exit"(node) {
let nodesToCheck;
if (node.type === "IfStatement") {
nodesToCheck = [node.consequent];
if (node.alternate) {
nodesToCheck.push(node.alternate);
}
} else {
nodesToCheck = [node.body];
}
for (const nodeToCheck of nodesToCheck) {
const lastToken = sourceCode.getLastToken(nodeToCheck);
if (astUtils.isSemicolonToken(lastToken)) {
const tokenBeforeLast = sourceCode.getTokenBefore(lastToken);
const tokenAfterLast = sourceCode.getTokenAfter(lastToken);
// override indentation of `;` only if its line looks like a semicolon-first style line
if (
!astUtils.isTokenOnSameLine(tokenBeforeLast, lastToken) &&
tokenAfterLast &&
astUtils.isTokenOnSameLine(lastToken, tokenAfterLast)
) {
offsets.setDesiredOffset(
lastToken,
sourceCode.getFirstToken(node),
0
);
}
}
}
},
ImportDeclaration(node) {
if (node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) {
const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken);
@ -1359,6 +1413,52 @@ module.exports = {
}
},
PropertyDefinition(node) {
const firstToken = sourceCode.getFirstToken(node);
const maybeSemicolonToken = sourceCode.getLastToken(node);
let keyLastToken = null;
// Indent key.
if (node.computed) {
const bracketTokenL = sourceCode.getTokenBefore(node.key, astUtils.isOpeningBracketToken);
const bracketTokenR = keyLastToken = sourceCode.getTokenAfter(node.key, astUtils.isClosingBracketToken);
const keyRange = [bracketTokenL.range[1], bracketTokenR.range[0]];
if (bracketTokenL !== firstToken) {
offsets.setDesiredOffset(bracketTokenL, firstToken, 0);
}
offsets.setDesiredOffsets(keyRange, bracketTokenL, 1);
offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0);
} else {
const idToken = keyLastToken = sourceCode.getFirstToken(node.key);
if (idToken !== firstToken) {
offsets.setDesiredOffset(idToken, firstToken, 1);
}
}
// Indent initializer.
if (node.value) {
const eqToken = sourceCode.getTokenBefore(node.value, astUtils.isEqToken);
const valueToken = sourceCode.getTokenAfter(eqToken);
offsets.setDesiredOffset(eqToken, keyLastToken, 1);
offsets.setDesiredOffset(valueToken, eqToken, 1);
if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
offsets.setDesiredOffset(maybeSemicolonToken, eqToken, 1);
}
} else if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
offsets.setDesiredOffset(maybeSemicolonToken, keyLastToken, 1);
}
},
StaticBlock(node) {
const openingCurly = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
const closingCurly = sourceCode.getLastToken(node);
addElementListIndent(node.body, openingCurly, closingCurly, options.StaticBlock.body);
},
SwitchStatement(node) {
const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken);
const closingCurly = sourceCode.getLastToken(node);

View file

@ -6,7 +6,7 @@
"use strict";
/* eslint sort-keys: ["error", "asc"] */
/* eslint sort-keys: ["error", "asc"] -- More readable for long list */
const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map");
@ -72,6 +72,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"lines-around-comment": () => require("./lines-around-comment"),
"lines-around-directive": () => require("./lines-around-directive"),
"lines-between-class-members": () => require("./lines-between-class-members"),
"logical-assignment-operators": () => require("./logical-assignment-operators"),
"max-classes-per-file": () => require("./max-classes-per-file"),
"max-depth": () => require("./max-depth"),
"max-len": () => require("./max-len"),
@ -103,6 +104,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-confusing-arrow": () => require("./no-confusing-arrow"),
"no-console": () => require("./no-console"),
"no-const-assign": () => require("./no-const-assign"),
"no-constant-binary-expression": () => require("./no-constant-binary-expression"),
"no-constant-condition": () => require("./no-constant-condition"),
"no-constructor-return": () => require("./no-constructor-return"),
"no-continue": () => require("./no-continue"),
@ -121,6 +123,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-empty-character-class": () => require("./no-empty-character-class"),
"no-empty-function": () => require("./no-empty-function"),
"no-empty-pattern": () => require("./no-empty-pattern"),
"no-empty-static-block": () => require("./no-empty-static-block"),
"no-eq-null": () => require("./no-eq-null"),
"no-eval": () => require("./no-eval"),
"no-ex-assign": () => require("./no-ex-assign"),
@ -165,6 +168,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-nested-ternary": () => require("./no-nested-ternary"),
"no-new": () => require("./no-new"),
"no-new-func": () => require("./no-new-func"),
"no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"),
"no-new-object": () => require("./no-new-object"),
"no-new-require": () => require("./no-new-require"),
"no-new-symbol": () => require("./no-new-symbol"),
@ -221,6 +225,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-unsafe-optional-chaining": () => require("./no-unsafe-optional-chaining"),
"no-unused-expressions": () => require("./no-unused-expressions"),
"no-unused-labels": () => require("./no-unused-labels"),
"no-unused-private-class-members": () => require("./no-unused-private-class-members"),
"no-unused-vars": () => require("./no-unused-vars"),
"no-use-before-define": () => require("./no-use-before-define"),
"no-useless-backreference": () => require("./no-useless-backreference"),
@ -254,6 +259,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
"prefer-named-capture-group": () => require("./prefer-named-capture-group"),
"prefer-numeric-literals": () => require("./prefer-numeric-literals"),
"prefer-object-has-own": () => require("./prefer-object-has-own"),
"prefer-object-spread": () => require("./prefer-object-spread"),
"prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
"prefer-reflect": () => require("./prefer-reflect"),

View file

@ -42,13 +42,13 @@ function isInitialized(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require or disallow initialization in variable declarations",
category: "Variables",
description: "Require or disallow initialization in variable declarations",
recommended: false,
url: "https://eslint.org/docs/rules/init-declarations"
},

View file

@ -36,13 +36,13 @@ const QUOTE_SETTINGS = {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce the consistent use of either double or single quotes in JSX attributes",
category: "Stylistic Issues",
description: "Enforce the consistent use of either double or single quotes in JSX attributes",
recommended: false,
url: "https://eslint.org/docs/rules/jsx-quotes"
},
@ -70,7 +70,7 @@ module.exports = {
* @public
*/
function usesExpectedQuotes(node) {
return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote);
return node.value.includes(setting.quote) || astUtils.isSurroundedBy(node.raw, setting.quote);
}
return {

Some files were not shown because too many files have changed in this diff Show more