Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: Behavior of CLI when no arguments are passed #17644

Merged
merged 13 commits into from
Dec 21, 2023
22 changes: 22 additions & 0 deletions docs/src/use/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ Please note that when passing a glob as a parameter, it is expanded by your shel
npx eslint "lib/**"
```

If you are using a [flat configuration file](./configure/configuration-files-new) (`eslint.config.js`), you can also omit the file arguments and ESLint will use `.`. For instance, these two lines perform the same operation:

```shell
npx eslint .
npx eslint
```

If you are not using a flat configuration file, running ESLint without file arguments results in an error.

**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. Please refer to your package manager's documentation for the correct syntax.

## Pass Multiple Values to an Option
Expand Down Expand Up @@ -112,6 +121,7 @@ Miscellaneous:
--no-error-on-unmatched-pattern Prevent errors when pattern is unmatched
--exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false
--no-warn-ignored Suppress warnings when the file list includes ignored files. *Flat Config Mode Only*
--pass-on-no-patterns Exit with exit code 0 in case no file patterns are passed
--debug Output debugging information
-h, --help Show help
-v, --version Output the version number
Expand Down Expand Up @@ -734,6 +744,18 @@ npx eslint --exit-on-fatal-error file.js
npx eslint --no-warn-ignored --max-warnings 0 ignored-file.js
```

#### `--pass-on-no-patterns`

This option allows ESLint to exit with code 0 when no file or directory patterns are passed. Without this option, ESLint assumes you want to use `.` as the pattern. (When running in legacy eslintrc mode, ESLint will exit with code 1.)

* **Argument Type**: No argument.

##### `--pass-on-no-patterns` example

```shell
npx eslint --pass-on-no-patterns
```

#### `--debug`

This option outputs debugging information to the console. Add this flag to an ESLint command line invocation in order to get extra debugging information while the command runs.
Expand Down
6 changes: 4 additions & 2 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ async function translateOptions({
resolvePluginsRelativeTo,
rule,
rulesdir,
warnIgnored
warnIgnored,
passOnNoPatterns
}, configType) {

let overrideConfig, overrideConfigFile;
Expand Down Expand Up @@ -187,7 +188,8 @@ async function translateOptions({
fixTypes: fixType,
ignore,
overrideConfig,
overrideConfigFile
overrideConfigFile,
passOnNoPatterns
};

if (configType === "flat") {
Expand Down
33 changes: 24 additions & 9 deletions lib/eslint/eslint-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,30 @@ class AllFilesIgnoredError extends Error {

/**
* 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.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is a non-empty string.
*/
function isNonEmptyString(x) {
return typeof x === "string" && x.trim() !== "";
function isNonEmptyString(value) {
return typeof value === "string" && value.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.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is an array of non-empty strings.
*/
function isArrayOfNonEmptyString(x) {
return Array.isArray(x) && x.every(isNonEmptyString);
function isArrayOfNonEmptyString(value) {
return Array.isArray(value) && value.length && value.every(isNonEmptyString);
nzakas marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Check if a given value is an empty array or an array of non-empty strings.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is an empty array or an array of non-empty
* strings.
*/
function isEmptyArrayOrArrayOfNonEmptyString(value) {
return Array.isArray(value) && value.every(isNonEmptyString);
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -676,6 +686,7 @@ function processOptions({
overrideConfigFile = null,
plugins = {},
warnIgnored = true,
passOnNoPatterns = false,
...unknownOptions
}) {
const errors = [];
Expand Down Expand Up @@ -759,7 +770,7 @@ function processOptions({
if (typeof ignore !== "boolean") {
errors.push("'ignore' must be a boolean.");
}
if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
}
if (typeof overrideConfig !== "object") {
Expand All @@ -768,6 +779,9 @@ function processOptions({
if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
}
if (typeof passOnNoPatterns !== "boolean") {
errors.push("'passOnNoPatterns' must be a boolean.");
}
if (typeof plugins !== "object") {
errors.push("'plugins' must be an object or null.");
} else if (plugins !== null && Object.keys(plugins).includes("")) {
Expand Down Expand Up @@ -800,6 +814,7 @@ function processOptions({
globInputPaths,
ignore,
ignorePatterns,
passOnNoPatterns,
warnIgnored
};
}
Expand Down
62 changes: 42 additions & 20 deletions lib/eslint/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const { version } = require("../../package.json");
* @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.
* @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files.
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
*/

/**
Expand Down Expand Up @@ -97,38 +99,48 @@ const privateMembersMap = new WeakMap();

/**
* 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.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is a non-empty string.
*/
function isNonEmptyString(x) {
return typeof x === "string" && x.trim() !== "";
function isNonEmptyString(value) {
return typeof value === "string" && value.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.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is an array of non-empty strings.
*/
function isArrayOfNonEmptyString(x) {
return Array.isArray(x) && x.every(isNonEmptyString);
function isArrayOfNonEmptyString(value) {
return Array.isArray(value) && value.length && value.every(isNonEmptyString);
}

/**
* Check if a given value is an empty array or an array of non-empty strings.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is an empty array or an array of non-empty
* strings.
*/
function isEmptyArrayOrArrayOfNonEmptyString(value) {
return Array.isArray(value) && value.every(isNonEmptyString);
}

/**
* 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.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is valid fix type.
*/
function isFixType(x) {
return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
function isFixType(value) {
return value === "directive" || value === "problem" || value === "suggestion" || value === "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.
* @param {any} value The value to check.
* @returns {boolean} `true` if `value` is an array of fix types.
*/
function isFixTypeArray(x) {
return Array.isArray(x) && x.every(isFixType);
function isFixTypeArray(value) {
return Array.isArray(value) && value.every(isFixType);
}

/**
Expand Down Expand Up @@ -169,6 +181,7 @@ function processOptions({
resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature.
rulePaths = [],
useEslintrc = true,
passOnNoPatterns = false,
...unknownOptions
}) {
const errors = [];
Expand Down Expand Up @@ -225,7 +238,7 @@ function processOptions({
if (typeof errorOnUnmatchedPattern !== "boolean") {
errors.push("'errorOnUnmatchedPattern' must be a boolean.");
}
if (!isArrayOfNonEmptyString(extensions) && extensions !== null) {
if (!isEmptyArrayOrArrayOfNonEmptyString(extensions) && extensions !== null) {
errors.push("'extensions' must be an array of non-empty strings or null.");
}
if (typeof fix !== "boolean" && typeof fix !== "function") {
Expand Down Expand Up @@ -271,12 +284,15 @@ function processOptions({
) {
errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null.");
}
if (!isArrayOfNonEmptyString(rulePaths)) {
if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) {
errors.push("'rulePaths' must be an array of non-empty strings.");
}
if (typeof useEslintrc !== "boolean") {
errors.push("'useEslintrc' must be a boolean.");
}
if (typeof passOnNoPatterns !== "boolean") {
errors.push("'passOnNoPatterns' must be a boolean.");
}

if (errors.length > 0) {
throw new ESLintInvalidOptionsError(errors);
Expand All @@ -300,7 +316,8 @@ function processOptions({
reportUnusedDisableDirectives,
resolvePluginsRelativeTo,
rulePaths,
useEslintrc
useEslintrc,
passOnNoPatterns
};
}

Expand Down Expand Up @@ -541,10 +558,15 @@ class ESLint {
* @returns {Promise<LintResult[]>} The results of linting the file patterns given.
*/
async lintFiles(patterns) {
const { cliEngine, options } = privateMembersMap.get(this);

if (options.passOnNoPatterns && (patterns === "" || (Array.isArray(patterns) && patterns.length === 0))) {
return [];
}

if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
}
const { cliEngine } = privateMembersMap.get(this);

return processCLIEngineLintReport(
cliEngine,
Expand Down
40 changes: 36 additions & 4 deletions lib/eslint/flat-eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
* when a string.
* @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
*/

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -738,16 +740,46 @@ class FlatESLint {
* @returns {Promise<LintResult[]>} The results of linting the file patterns given.
*/
async lintFiles(patterns) {
if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
}

let normalizedPatterns = patterns;
const {
cacheFilePath,
lintResultCache,
linter,
options: eslintOptions
} = privateMembers.get(this);

/*
* Special cases:
* 1. `patterns` is an empty string
* 2. `patterns` is an empty array
*
* In both cases, we use the cwd as the directory to lint.
*/
if (patterns === "" || Array.isArray(patterns) && patterns.length === 0) {

/*
* Special case: If `passOnNoPatterns` is true, then we just exit
* without doing any work.
*/
if (eslintOptions.passOnNoPatterns) {
return [];
}

normalizedPatterns = ["."];
} else {

if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
}

if (typeof patterns === "string") {
normalizedPatterns = [patterns];
}
}

debug(`Using file patterns: ${normalizedPatterns}`);

const configs = await calculateConfigArray(this, eslintOptions);
const {
allowInlineConfig,
Expand Down Expand Up @@ -779,7 +811,7 @@ class FlatESLint {
}

const filePaths = await findFiles({
patterns: typeof patterns === "string" ? [patterns] : patterns,
patterns: normalizedPatterns,
cwd,
globInputPaths,
configs,
Expand Down
8 changes: 8 additions & 0 deletions lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const optionator = require("optionator");
* @property {boolean} quiet Report errors only
* @property {boolean} [version] Output the version number
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
* @property {string[]} _ Positional filenames or patterns
*/

Expand Down Expand Up @@ -370,6 +372,12 @@ module.exports = function(usingFlatConfig) {
description: "Exit with exit code 2 in case of fatal error"
},
warnIgnoredFlag,
{
option: "pass-on-no-patterns",
type: "Boolean",
default: false,
description: "Exit with exit code 0 in case no file patterns are passed"
},
{
option: "debug",
type: "Boolean",
Expand Down
7 changes: 7 additions & 0 deletions tests/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,13 @@ describe("cli", () => {
assert.strictEqual(exit, useFlatConfig ? 0 : 2);
});

it(`should not lint anything when no files are passed if --pass-on-no-patterns is passed with configType:${configType}`, async () => {
const exit = await cli.execute("--pass-on-no-patterns", null, useFlatConfig);

assert.isFalse(log.info.called);
assert.strictEqual(exit, 0);
});

it(`should suppress the warning if --no-warn-ignored is passed and an ignored file is passed via stdin with configType:${configType}`, async () => {
const options = useFlatConfig
? `--config ${getFixturePath("eslint.config_with_ignores.js")}`
Expand Down