diff --git a/CHANGELOG.md b/CHANGELOG.md
index efb9cc93035a..61aa641c59f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+v8.51.0 - October 6, 2023
+
+* [`1ef39ea`](https://github.com/eslint/eslint/commit/1ef39ea5b884453be717ebc929155d7eb584dcbf) chore: upgrade @eslint/js@8.51.0 (#17624) (Milos Djermanovic)
+* [`f8c7403`](https://github.com/eslint/eslint/commit/f8c7403255c11e99c402860aef3c0179f2b16628) chore: package.json update for @eslint/js release (ESLint Jenkins)
+* [`f976b2f`](https://github.com/eslint/eslint/commit/f976b2f7bfe7cc78bb649f8b37e90fd519ff3bcc) fix: make rule severity case-sensitive in flat config (#17619) (Milos Djermanovic)
+* [`0edfe36`](https://github.com/eslint/eslint/commit/0edfe369aa5bd80a98053022bb4c6b1ea0155f44) fix: Ensure crash error messages are not duplicated (#17584) (Nicholas C. Zakas)
+* [`ee5be81`](https://github.com/eslint/eslint/commit/ee5be81fa3c4fe801c2f653854f098ed6a84dcef) docs: default to `sourceType: "module"` in rule examples (#17615) (Francesco Trotta)
+* [`dd79abc`](https://github.com/eslint/eslint/commit/dd79abc0c1857b1d765acc312c0d6518e40d31c9) fix: `eslint-disable` to be able to parse quoted rule names (#17612) (Yosuke Ota)
+* [`d2f6801`](https://github.com/eslint/eslint/commit/d2f68019b8882278877801c5ef2f74d55e2a10c1) fix: Ensure correct code path for && followed by ?? (#17618) (Nicholas C. Zakas)
+* [`2665552`](https://github.com/eslint/eslint/commit/2665552ba0057e8603f9fbece0fd236f189f5cf3) test: fix flat config linter tests to use Linter in flat config mode (#17616) (Milos Djermanovic)
+* [`1aa26df`](https://github.com/eslint/eslint/commit/1aa26df9fbcfdf5b895743c6d2d3a216479544b1) docs: Add more examples for multiline-ternary (#17610) (George Ashiotis)
+* [`47d0b44`](https://github.com/eslint/eslint/commit/47d0b446964f44d70b9457ecc368e721e1dc7c11) docs: Update README (GitHub Actions Bot)
+* [`dbf831e`](https://github.com/eslint/eslint/commit/dbf831e31f8eea0bc94df96cd33255579324b66e) docs: use generated og image (#17601) (Percy Ma)
+* [`0a9c433`](https://github.com/eslint/eslint/commit/0a9c43339a4adef24ef83034d0b078dd279cc977) feat: Add `--no-warn-ignored` CLI option for flat config (#17569) (Domantas Petrauskas)
+* [`1866da5`](https://github.com/eslint/eslint/commit/1866da5e1d931787256ecb825a803cac5835b71c) docs: Update README (GitHub Actions Bot)
+* [`7b77bcc`](https://github.com/eslint/eslint/commit/7b77bccbb51bd36b2d20fea61bc782545c4029b3) chore: Refactor CodePathState (#17510) (Nicholas C. Zakas)
+* [`977e67e`](https://github.com/eslint/eslint/commit/977e67ec274a05cb7391665b5e3453e7f72f72b2) feat: logical-assignment-operators to report expressions with 3 operands (#17600) (Yosuke Ota)
+* [`bc77c9a`](https://github.com/eslint/eslint/commit/bc77c9af12539f350ef19e30611a153a5b869c6b) chore: Document and refactor ForkContext (#17566) (Nicholas C. Zakas)
+* [`24e1f14`](https://github.com/eslint/eslint/commit/24e1f140ec68659e55c1ace0d7500addb135a2b4) chore: Refactor and document CodePath (#17558) (Nicholas C. Zakas)
+
v8.50.0 - September 22, 2023
* [`f8a8a2d`](https://github.com/eslint/eslint/commit/f8a8a2d6b45c82f94a574623759b6e3d2af193f3) chore: upgrade @eslint/js@8.50.0 (#17599) (Milos Djermanovic)
diff --git a/README.md b/README.md
index 0b8296193241..7aa4019ed87a 100644
--- a/README.md
+++ b/README.md
@@ -288,8 +288,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
Platinum Sponsors
Gold Sponsors
Silver Sponsors
-
Bronze Sponsors
-
+
Bronze Sponsors
+
## Technology Sponsors
diff --git a/bin/eslint.js b/bin/eslint.js
index 7094ac77bc4b..5c7972cc086e 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -92,6 +92,14 @@ function getErrorMessage(error) {
return util.format("%o", error);
}
+/**
+ * Tracks error messages that are shown to the user so we only ever show the
+ * same message once.
+ * @type {Set}
+ */
+
+const displayedErrors = new Set();
+
/**
* Catch and report unexpected error.
* @param {any} error The thrown error object.
@@ -101,14 +109,17 @@ function onFatalError(error) {
process.exitCode = 2;
const { version } = require("../package.json");
- const message = getErrorMessage(error);
-
- console.error(`
+ const message = `
Oops! Something went wrong! :(
ESLint: ${version}
-${message}`);
+${getErrorMessage(error)}`;
+
+ if (!displayedErrors.has(message)) {
+ console.error(message);
+ displayedErrors.add(message);
+ }
}
//------------------------------------------------------------------------------
diff --git a/docs/.eleventy.js b/docs/.eleventy.js
index 94a202112543..68c6604cf8d0 100644
--- a/docs/.eleventy.js
+++ b/docs/.eleventy.js
@@ -205,11 +205,12 @@ module.exports = function(eleventyConfig) {
}
// See https://github.com/eslint/eslint.org/blob/ac38ab41f99b89a8798d374f74e2cce01171be8b/src/playground/App.js#L44
- const parserOptions = tokens[index].info?.split("correct ")[1]?.trim();
+ const parserOptionsJSON = tokens[index].info?.split("correct ")[1]?.trim();
+ const parserOptions = { sourceType: "module", ...(parserOptionsJSON && JSON.parse(parserOptionsJSON)) };
const { content } = tokens[index + 1];
const state = encodeToBase64(
JSON.stringify({
- ...(parserOptions && { options: { parserOptions: JSON.parse(parserOptions) } }),
+ options: { parserOptions },
text: content
})
);
diff --git a/docs/package.json b/docs/package.json
index 120aa91c60c3..1b2019bb4cb6 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,7 +1,7 @@
{
"name": "docs-eslint",
"private": true,
- "version": "8.50.0",
+ "version": "8.51.0",
"description": "",
"main": "index.js",
"keywords": [],
diff --git a/docs/src/_includes/layouts/base.html b/docs/src/_includes/layouts/base.html
index 49bdf8522c89..86ea680c3cb0 100644
--- a/docs/src/_includes/layouts/base.html
+++ b/docs/src/_includes/layouts/base.html
@@ -13,11 +13,19 @@
{% set page_title = site[hook].title %}
{% endif %}
+ {% set rule_meta = rules_meta[title] %}
{% set page_title = site.shared.title_format | replace("PAGE_TITLE", page_title) %}
- {% set cover_image = ["https://", site.hostname, "/icon-512.png" ] | join %}
- {% set cover_image_alt = site.shared.eslint_logo_alt %}
{% set page_desc = site.shared.description %}
{% set relative_page_url = page.url | url | prettyURL %}
+ {% set cover_image = [
+ "https://", site.hostname, "/og",
+ "?title=", title | urlencode, "&summary=", page_desc | urlencode,
+ "&is_rule=", rule_meta !== undefined,
+ "&recommended=", rule_meta.docs.recommended,
+ "&fixable=", rule_meta.fixable,
+ "&suggestions=", rule_meta.hasSuggestions
+ ] | join %}
+ {% set cover_image_alt = site.shared.eslint_logo_alt %}
{% set page_url = ["https://", site.hostname, relative_page_url ] | join %}
diff --git a/docs/src/rules/camelcase.md b/docs/src/rules/camelcase.md
index 46757fd53b06..aa75b231becd 100644
--- a/docs/src/rules/camelcase.md
+++ b/docs/src/rules/camelcase.md
@@ -328,7 +328,7 @@ function UNSAFE_componentWillMount() {
// ...
}
-function UNSAFE_componentWillMount() {
+function UNSAFE_componentWillReceiveProps() {
// ...
}
```
diff --git a/docs/src/rules/comma-spacing.md b/docs/src/rules/comma-spacing.md
index b69378563211..9bc7f82dd246 100644
--- a/docs/src/rules/comma-spacing.md
+++ b/docs/src/rules/comma-spacing.md
@@ -60,7 +60,7 @@ var arr = [1 , 2];
var obj = {"foo": "bar" ,"baz": "qur"};
foo(a ,b);
new Foo(a ,b);
-function foo(a ,b){}
+function baz(a ,b){}
a ,b
```
@@ -80,7 +80,7 @@ var arr = [1,, 3]
var obj = {"foo": "bar", "baz": "qur"};
foo(a, b);
new Foo(a, b);
-function foo(a, b){}
+function qur(a, b){}
a, b
```
@@ -131,7 +131,7 @@ var foo = 1, bar = 2;
var arr = [1 , 2];
var obj = {"foo": "bar", "baz": "qur"};
new Foo(a,b);
-function foo(a,b){}
+function baz(a,b){}
a, b
```
@@ -151,7 +151,7 @@ var arr = [1 ,,3]
var obj = {"foo": "bar" ,"baz": "qur"};
foo(a ,b);
new Foo(a ,b);
-function foo(a ,b){}
+function qur(a ,b){}
a ,b
```
diff --git a/docs/src/rules/comma-style.md b/docs/src/rules/comma-style.md
index 99d14b27f4fa..6936cdbcb244 100644
--- a/docs/src/rules/comma-style.md
+++ b/docs/src/rules/comma-style.md
@@ -69,7 +69,7 @@ var foo = 1
var foo = ["apples"
, "oranges"];
-function bar() {
+function baz() {
return {
"a": 1
,"b:": 2
@@ -94,7 +94,7 @@ var foo = 1,
var foo = ["apples",
"oranges"];
-function bar() {
+function baz() {
return {
"a": 1,
"b:": 2
@@ -119,7 +119,7 @@ var foo = 1,
var foo = ["apples",
"oranges"];
-function bar() {
+function baz() {
return {
"a": 1,
"b:": 2
@@ -144,7 +144,7 @@ var foo = 1
var foo = ["apples"
,"oranges"];
-function bar() {
+function baz() {
return {
"a": 1
,"b:": 2
diff --git a/docs/src/rules/consistent-return.md b/docs/src/rules/consistent-return.md
index 2d5f4e3f2ca7..797c9b08ca01 100644
--- a/docs/src/rules/consistent-return.md
+++ b/docs/src/rules/consistent-return.md
@@ -48,7 +48,7 @@ function doSomething(condition) {
}
}
-function doSomething(condition) {
+function doSomethingElse(condition) {
if (condition) {
return true;
}
diff --git a/docs/src/rules/default-param-last.md b/docs/src/rules/default-param-last.md
index 7f64f7c4464d..63672a9103ec 100644
--- a/docs/src/rules/default-param-last.md
+++ b/docs/src/rules/default-param-last.md
@@ -28,7 +28,7 @@ Examples of **incorrect** code for this rule:
function f(a = 0, b) {}
-function f(a, b = 0, c) {}
+function g(a, b = 0, c) {}
```
:::
diff --git a/docs/src/rules/function-paren-newline.md b/docs/src/rules/function-paren-newline.md
index 4ba5a922c8f4..e6a067cf5f23 100644
--- a/docs/src/rules/function-paren-newline.md
+++ b/docs/src/rules/function-paren-newline.md
@@ -49,9 +49,9 @@ Examples of **incorrect** code for this rule with the `"always"` option:
function foo(bar, baz) {}
-var foo = function(bar, baz) {};
+var qux = function(bar, baz) {};
-var foo = (bar, baz) => {};
+var qux = (bar, baz) => {};
foo(bar, baz);
```
@@ -70,11 +70,11 @@ function foo(
baz
) {}
-var foo = function(
+var qux = function(
bar, baz
) {};
-var foo = (
+var qux = (
bar,
baz
) => {};
@@ -99,11 +99,11 @@ function foo(
baz
) {}
-var foo = function(
+var qux = function(
bar, baz
) {};
-var foo = (
+var qux = (
bar,
baz
) => {};
@@ -125,12 +125,12 @@ Examples of **correct** code for this rule with the `"never"` option:
function foo(bar, baz) {}
-function foo(bar,
+function qux(bar,
baz) {}
-var foo = function(bar, baz) {};
+var foobar = function(bar, baz) {};
-var foo = (bar, baz) => {};
+var foobar = (bar, baz) => {};
foo(bar, baz);
@@ -151,11 +151,11 @@ function foo(bar,
baz
) {}
-var foo = function(
+var qux = function(
bar, baz
) {};
-var foo = (
+var qux = (
bar,
baz) => {};
@@ -180,12 +180,12 @@ Examples of **correct** code for this rule with the default `"multiline"` option
function foo(bar, baz) {}
-var foo = function(
+var foobar = function(
bar,
baz
) {};
-var foo = (bar, baz) => {};
+var foobar = (bar, baz) => {};
foo(bar, baz, qux);
@@ -213,11 +213,11 @@ function foo(bar,
baz
) {}
-var foo = function(bar,
+var qux = function(bar,
baz
) {};
-var foo = (
+var qux = (
bar,
baz) => {};
@@ -243,9 +243,9 @@ Examples of **correct** code for this rule with the `"consistent"` option:
function foo(bar,
baz) {}
-var foo = function(bar, baz) {};
+var qux = function(bar, baz) {};
-var foo = (
+var qux = (
bar,
baz
) => {};
@@ -274,11 +274,11 @@ function foo(bar,
baz
) {}
-var foo = function(bar,
+var foobar = function(bar,
baz
) {};
-var foo = (
+var foobar = (
bar,
baz) => {};
@@ -306,9 +306,9 @@ function foo(
baz
) {}
-var foo = function(bar, baz) {};
+var qux = function(bar, baz) {};
-var foo = (
+var qux = (
bar
) => {};
@@ -333,13 +333,13 @@ function foo(
baz
) {}
-function foo(bar, baz, qux) {}
+function foobar(bar, baz, qux) {}
-var foo = function(
+var barbaz = function(
bar, baz
) {};
-var foo = (bar,
+var barbaz = (bar,
baz) => {};
foo(bar,
@@ -357,13 +357,13 @@ Examples of **correct** code for this rule with the `{ "minItems": 3 }` option:
function foo(bar, baz) {}
-var foo = function(
+var foobar = function(
bar,
baz,
qux
) {};
-var foo = (
+var foobar = (
bar, baz, qux
) => {};
diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md
index e960468b3807..8460acb89ad1 100644
--- a/docs/src/rules/global-require.md
+++ b/docs/src/rules/global-require.md
@@ -32,7 +32,7 @@ This rule requires all calls to `require()` to be at the top level of the module
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint global-require: "error"*/
@@ -76,7 +76,7 @@ try {
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint global-require: "error"*/
diff --git a/docs/src/rules/id-length.md b/docs/src/rules/id-length.md
index adf5e8978175..aed5ada0a7c6 100644
--- a/docs/src/rules/id-length.md
+++ b/docs/src/rules/id-length.md
@@ -160,10 +160,10 @@ try {
}
var myObj = { apple: 1 };
(value) => { value * value };
-function foobar(value = 0) { }
+function foobaz(value = 0) { }
class MyClass { }
class Foobar { method() {} }
-function foobar(...args) { }
+function barbaz(...args) { }
var { prop } = {};
var [longName] = foo;
var { a: [prop] } = {};
diff --git a/docs/src/rules/lines-around-directive.md b/docs/src/rules/lines-around-directive.md
index fb71775c7f16..8a83df1afd7c 100644
--- a/docs/src/rules/lines-around-directive.md
+++ b/docs/src/rules/lines-around-directive.md
@@ -60,7 +60,7 @@ This is the default option.
Examples of **incorrect** code for this rule with the `"always"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", "always"] */
@@ -92,7 +92,7 @@ function foo() {
Examples of **correct** code for this rule with the `"always"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", "always"] */
@@ -132,7 +132,7 @@ function foo() {
Examples of **incorrect** code for this rule with the `"never"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", "never"] */
@@ -171,7 +171,7 @@ function foo() {
Examples of **correct** code for this rule with the `"never"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", "never"] */
@@ -205,7 +205,7 @@ function foo() {
Examples of **incorrect** code for this rule with the `{ "before": "never", "after": "always" }` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */
@@ -240,7 +240,7 @@ function foo() {
Examples of **correct** code for this rule with the `{ "before": "never", "after": "always" }` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */
@@ -276,7 +276,7 @@ function foo() {
Examples of **incorrect** code for this rule with the `{ "before": "always", "after": "never" }` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */
@@ -312,7 +312,7 @@ function foo() {
Examples of **correct** code for this rule with the `{ "before": "always", "after": "never" }` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */
diff --git a/docs/src/rules/logical-assignment-operators.md b/docs/src/rules/logical-assignment-operators.md
index 058afcd3ad78..1b59cfa70bba 100644
--- a/docs/src/rules/logical-assignment-operators.md
+++ b/docs/src/rules/logical-assignment-operators.md
@@ -10,7 +10,7 @@ For example `a = a || b` can be shortened to `a ||= b`.
## Rule Details
-This rule requires or disallows logical assignment operator shorthand.
+This rule requires or disallows logical assignment operator shorthand.
### Options
@@ -27,6 +27,9 @@ Object option (only available if string option is set to `"always"`):
#### always
+This option checks for expressions that can be shortened using logical assignment operator. For example, `a = a || b` can be shortened to `a ||= b`.
+Expressions with associativity such as `a = a || b || c` are reported as being able to be shortened to `a ||= b || c` unless the evaluation order is explicitly defined using parentheses, such as `a = (a || b) || c`.
+
Examples of **incorrect** code for this rule with the default `"always"` option:
::: incorrect
@@ -40,6 +43,9 @@ a = a ?? b
a || (a = b)
a && (a = b)
a ?? (a = b)
+a = a || b || c
+a = a && b && c
+a = a ?? b ?? c
```
:::
@@ -58,6 +64,8 @@ a = b || c
a || (b = c)
if (a) a = b
+
+a = (a || b) || c
```
:::
diff --git a/docs/src/rules/max-statements-per-line.md b/docs/src/rules/max-statements-per-line.md
index 238ade787e86..76a49e3a09b1 100644
--- a/docs/src/rules/max-statements-per-line.md
+++ b/docs/src/rules/max-statements-per-line.md
@@ -40,7 +40,7 @@ if (condition) { bar = 1; }
for (var i = 0; i < length; ++i) { bar = 1; }
switch (discriminant) { default: break; }
function foo() { bar = 1; }
-var foo = function foo() { bar = 1; };
+var qux = function qux() { bar = 1; };
(function foo() { bar = 1; })();
```
@@ -58,7 +58,7 @@ if (condition) bar = 1;
for (var i = 0; i < length; ++i);
switch (discriminant) { default: }
function foo() { }
-var foo = function foo() { };
+var qux = function qux() { };
(function foo() { })();
```
@@ -76,7 +76,7 @@ if (condition) { bar = 1; } else { baz = 2; }
for (var i = 0; i < length; ++i) { bar = 1; baz = 2; }
switch (discriminant) { case 'test': break; default: break; }
function foo() { bar = 1; baz = 2; }
-var foo = function foo() { bar = 1; };
+var qux = function qux() { bar = 1; };
(function foo() { bar = 1; baz = 2; })();
```
@@ -94,7 +94,7 @@ if (condition) bar = 1; if (condition) baz = 2;
for (var i = 0; i < length; ++i) { bar = 1; }
switch (discriminant) { default: break; }
function foo() { bar = 1; }
-var foo = function foo() { bar = 1; };
+var qux = function qux() { bar = 1; };
(function foo() { var bar = 1; })();
```
diff --git a/docs/src/rules/multiline-ternary.md b/docs/src/rules/multiline-ternary.md
index 7e4688d637ae..f828bc1cd40a 100644
--- a/docs/src/rules/multiline-ternary.md
+++ b/docs/src/rules/multiline-ternary.md
@@ -18,9 +18,14 @@ var foo = bar > baz ? value1 : value2;
The above can be rewritten as the following to improve readability and more clearly delineate the operands:
```js
+
var foo = bar > baz ?
value1 :
value2;
+
+var foo = bar > baz
+ ? value1
+ : value2;
```
## Rule Details
@@ -74,6 +79,12 @@ foo > bar ?
value1 :
value2) :
value3;
+
+foo > bar
+ ? (baz > qux
+ ? value1
+ : value2)
+ : value3;
```
:::
@@ -126,6 +137,12 @@ foo > bar &&
bar > baz ?
value1 :
value2;
+
+foo > bar
+ ? baz > qux
+ ? value1
+ : value2
+ : value3;
```
:::
diff --git a/docs/src/rules/newline-before-return.md b/docs/src/rules/newline-before-return.md
index 41fb7726c7b2..1980f5bd3463 100644
--- a/docs/src/rules/newline-before-return.md
+++ b/docs/src/rules/newline-before-return.md
@@ -49,14 +49,14 @@ Examples of **incorrect** code for this rule:
```js
/*eslint newline-before-return: "error"*/
-function foo(bar) {
+function foo1(bar) {
if (!bar) {
return;
}
return bar;
}
-function foo(bar) {
+function foo2(bar) {
if (!bar) {
return;
}
@@ -75,30 +75,30 @@ Examples of **correct** code for this rule:
```js
/*eslint newline-before-return: "error"*/
-function foo() {
+function foo1() {
return;
}
-function foo() {
+function foo2() {
return;
}
-function foo(bar) {
+function foo3(bar) {
if (!bar) return;
}
-function foo(bar) {
+function foo4(bar) {
if (!bar) { return };
}
-function foo(bar) {
+function foo5(bar) {
if (!bar) {
return;
}
}
-function foo(bar) {
+function foo6(bar) {
if (!bar) {
return;
}
@@ -106,14 +106,14 @@ function foo(bar) {
return bar;
}
-function foo(bar) {
+function foo7(bar) {
if (!bar) {
return;
}
}
-function foo() {
+function foo8() {
// comment
return;
diff --git a/docs/src/rules/no-catch-shadow.md b/docs/src/rules/no-catch-shadow.md
index 23328e42fef2..c875f9fe4a92 100644
--- a/docs/src/rules/no-catch-shadow.md
+++ b/docs/src/rules/no-catch-shadow.md
@@ -39,13 +39,13 @@ try {
}
-function err() {
+function error() {
// ...
};
try {
throw "problem";
-} catch (err) {
+} catch (error) {
}
```
@@ -67,7 +67,7 @@ try {
}
-function err() {
+function error() {
// ...
};
diff --git a/docs/src/rules/no-cond-assign.md b/docs/src/rules/no-cond-assign.md
index b72c2d09c672..7cb02d7c32fb 100644
--- a/docs/src/rules/no-cond-assign.md
+++ b/docs/src/rules/no-cond-assign.md
@@ -45,8 +45,7 @@ if (x = 0) {
}
// Practical example that is similar to an error
-function setHeight(someNode) {
- "use strict";
+var setHeight = function (someNode) {
do {
someNode.height = "100px";
} while (someNode = someNode.parentNode);
@@ -69,16 +68,14 @@ if (x === 0) {
}
// Practical example that wraps the assignment in parentheses
-function setHeight(someNode) {
- "use strict";
+var setHeight = function (someNode) {
do {
someNode.height = "100px";
} while ((someNode = someNode.parentNode));
}
// Practical example that wraps the assignment and tests for 'null'
-function setHeight(someNode) {
- "use strict";
+var setHeight = function (someNode) {
do {
someNode.height = "100px";
} while ((someNode = someNode.parentNode) !== null);
@@ -103,24 +100,21 @@ if (x = 0) {
}
// Practical example that is similar to an error
-function setHeight(someNode) {
- "use strict";
+var setHeight = function (someNode) {
do {
someNode.height = "100px";
} while (someNode = someNode.parentNode);
}
// Practical example that wraps the assignment in parentheses
-function setHeight(someNode) {
- "use strict";
+var setHeight = function (someNode) {
do {
someNode.height = "100px";
} while ((someNode = someNode.parentNode));
}
// Practical example that wraps the assignment and tests for 'null'
-function setHeight(someNode) {
- "use strict";
+var setHeight = function (someNode) {
do {
someNode.height = "100px";
} while ((someNode = someNode.parentNode) !== null);
diff --git a/docs/src/rules/no-delete-var.md b/docs/src/rules/no-delete-var.md
index 65a5aa1614dd..f66aec5879fb 100644
--- a/docs/src/rules/no-delete-var.md
+++ b/docs/src/rules/no-delete-var.md
@@ -15,7 +15,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-delete-var: "error"*/
diff --git a/docs/src/rules/no-dupe-args.md b/docs/src/rules/no-dupe-args.md
index 3cb9133c83fa..748d52c2e21f 100644
--- a/docs/src/rules/no-dupe-args.md
+++ b/docs/src/rules/no-dupe-args.md
@@ -16,7 +16,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-dupe-args: "error"*/
@@ -34,7 +34,7 @@ var bar = function (a, b, a) {
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-dupe-args: "error"*/
diff --git a/docs/src/rules/no-else-return.md b/docs/src/rules/no-else-return.md
index 917c44553908..79b4fe5fb01d 100644
--- a/docs/src/rules/no-else-return.md
+++ b/docs/src/rules/no-else-return.md
@@ -37,7 +37,7 @@ Examples of **incorrect** code for this rule:
```js
/*eslint no-else-return: "error"*/
-function foo() {
+function foo1() {
if (x) {
return y;
} else {
@@ -45,7 +45,7 @@ function foo() {
}
}
-function foo() {
+function foo2() {
if (x) {
return y;
} else if (z) {
@@ -55,7 +55,7 @@ function foo() {
}
}
-function foo() {
+function foo3() {
if (x) {
return y;
} else {
@@ -65,7 +65,7 @@ function foo() {
return t;
}
-function foo() {
+function foo4() {
if (error) {
return 'It failed';
} else {
@@ -76,7 +76,7 @@ function foo() {
}
// Two warnings for nested occurrences
-function foo() {
+function foo5() {
if (x) {
if (y) {
return y;
@@ -98,7 +98,7 @@ Examples of **correct** code for this rule:
```js
/*eslint no-else-return: "error"*/
-function foo() {
+function foo1() {
if (x) {
return y;
}
@@ -106,7 +106,7 @@ function foo() {
return z;
}
-function foo() {
+function foo2() {
if (x) {
return y;
} else if (z) {
@@ -116,7 +116,7 @@ function foo() {
}
}
-function foo() {
+function foo3() {
if (x) {
if (z) {
return y;
@@ -126,7 +126,7 @@ function foo() {
}
}
-function foo() {
+function foo4() {
if (error) {
return 'It failed';
} else if (loading) {
diff --git a/docs/src/rules/no-empty-function.md b/docs/src/rules/no-empty-function.md
index a9be4bb05d51..68aa2ad887b6 100644
--- a/docs/src/rules/no-empty-function.md
+++ b/docs/src/rules/no-empty-function.md
@@ -38,13 +38,13 @@ Examples of **incorrect** code for this rule:
function foo() {}
-var foo = function() {};
+var bar = function() {};
-var foo = () => {};
+var bar = () => {};
-function* foo() {}
+function* baz() {}
-var foo = function*() {};
+var bar = function*() {};
var obj = {
foo: function() {},
@@ -95,19 +95,19 @@ function foo() {
// do nothing.
}
-var foo = function() {
+var baz = function() {
// any clear comments.
};
-var foo = () => {
+var baz = () => {
bar();
};
-function* foo() {
+function* foobar() {
// do nothing.
}
-var foo = function*() {
+var baz = function*() {
// do nothing.
};
@@ -205,7 +205,7 @@ Examples of **correct** code for the `{ "allow": ["functions"] }` option:
function foo() {}
-var foo = function() {};
+var bar = function() {};
var obj = {
foo: function() {}
@@ -241,7 +241,7 @@ Examples of **correct** code for the `{ "allow": ["generatorFunctions"] }` optio
function* foo() {}
-var foo = function*() {};
+var bar = function*() {};
var obj = {
foo: function*() {}
diff --git a/docs/src/rules/no-empty-pattern.md b/docs/src/rules/no-empty-pattern.md
index 7edd933731a9..92000add1857 100644
--- a/docs/src/rules/no-empty-pattern.md
+++ b/docs/src/rules/no-empty-pattern.md
@@ -44,9 +44,9 @@ var [] = foo;
var {a: {}} = foo;
var {a: []} = foo;
function foo({}) {}
-function foo([]) {}
-function foo({a: {}}) {}
-function foo({a: []}) {}
+function bar([]) {}
+function baz({a: {}}) {}
+function qux({a: []}) {}
```
:::
@@ -61,7 +61,7 @@ Examples of **correct** code for this rule:
var {a = {}} = foo;
var {a = []} = foo;
function foo({a = {}}) {}
-function foo({a = []}) {}
+function bar({a = []}) {}
```
:::
@@ -84,12 +84,12 @@ Examples of **incorrect** code for this rule with the `{"allowObjectPatternsAsPa
/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/
function foo({a: {}}) {}
-var foo = function({a: {}}) {};
-var foo = ({a: {}}) => {};
-var foo = ({} = bar) => {};
-var foo = ({} = { bar: 1 }) => {};
+var bar = function({a: {}}) {};
+var bar = ({a: {}}) => {};
+var bar = ({} = bar) => {};
+var bar = ({} = { bar: 1 }) => {};
-function foo([]) {}
+function baz([]) {}
```
:::
@@ -102,10 +102,10 @@ Examples of **correct** code for this rule with the `{"allowObjectPatternsAsPara
/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/
function foo({}) {}
-var foo = function({}) {};
-var foo = ({}) => {};
+var bar = function({}) {};
+var bar = ({}) => {};
-function foo({} = {}) {}
+function baz({} = {}) {}
```
:::
diff --git a/docs/src/rules/no-extra-boolean-cast.md b/docs/src/rules/no-extra-boolean-cast.md
index e45509890f50..2408d6171746 100644
--- a/docs/src/rules/no-extra-boolean-cast.md
+++ b/docs/src/rules/no-extra-boolean-cast.md
@@ -75,7 +75,7 @@ Examples of **correct** code for this rule:
var foo = !!bar;
var foo = Boolean(bar);
-function foo() {
+function qux() {
return !!bar;
}
diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md
index 77bef24a7286..1a4d8e2d48a0 100644
--- a/docs/src/rules/no-extra-parens.md
+++ b/docs/src/rules/no-extra-parens.md
@@ -159,11 +159,11 @@ Examples of **correct** code for this rule with the `"all"` and `{ "returnAssign
```js
/* eslint no-extra-parens: ["error", "all", { "returnAssign": false }] */
-function a(b) {
+function a1(b) {
return (b = 1);
}
-function a(b) {
+function a2(b) {
return b ? (c = d) : (c = e);
}
diff --git a/docs/src/rules/no-func-assign.md b/docs/src/rules/no-func-assign.md
index a0f146203742..6346e2b8064f 100644
--- a/docs/src/rules/no-func-assign.md
+++ b/docs/src/rules/no-func-assign.md
@@ -27,8 +27,8 @@ Examples of **incorrect** code for this rule:
function foo() {}
foo = bar;
-function foo() {
- foo = bar;
+function baz() {
+ baz = bar;
}
var a = function hello() {
@@ -61,12 +61,12 @@ Examples of **correct** code for this rule:
var foo = function () {}
foo = bar;
-function foo(foo) { // `foo` is shadowed.
- foo = bar;
+function baz(baz) { // `baz` is shadowed.
+ baz = bar;
}
-function foo() {
- var foo = bar; // `foo` is shadowed.
+function qux() {
+ var qux = bar; // `qux` is shadowed.
}
```
diff --git a/docs/src/rules/no-inner-declarations.md b/docs/src/rules/no-inner-declarations.md
index 8828fabe7d2f..a81a45a61fa1 100644
--- a/docs/src/rules/no-inner-declarations.md
+++ b/docs/src/rules/no-inner-declarations.md
@@ -74,7 +74,7 @@ This rule has a string option:
Examples of **incorrect** code for this rule with the default `"functions"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-inner-declarations: "error"*/
@@ -104,7 +104,7 @@ class C {
Examples of **correct** code for this rule with the default `"functions"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-inner-declarations: "error"*/
@@ -139,7 +139,7 @@ if (foo) var a;
Examples of **incorrect** code for this rule with the `"both"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-inner-declarations: ["error", "both"]*/
@@ -171,7 +171,7 @@ class C {
Examples of **correct** code for this rule with the `"both"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-inner-declarations: ["error", "both"]*/
diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md
index 9e4a2aedffae..0c529a970650 100644
--- a/docs/src/rules/no-invalid-this.md
+++ b/docs/src/rules/no-invalid-this.md
@@ -49,7 +49,7 @@ With `"parserOptions": { "sourceType": "module" }` in the ESLint configuration,
Examples of **incorrect** code for this rule in strict mode:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-invalid-this: "error"*/
@@ -97,7 +97,7 @@ foo.forEach(function() {
Examples of **correct** code for this rule in strict mode:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-invalid-this: "error"*/
@@ -243,7 +243,7 @@ Set `"capIsConstructor"` to `false` if you want those functions to be treated as
Examples of **incorrect** code for this rule with `"capIsConstructor"` option set to `false`:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-invalid-this: ["error", { "capIsConstructor": false }]*/
@@ -271,7 +271,7 @@ Baz = function() {
Examples of **correct** code for this rule with `"capIsConstructor"` option set to `false`:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-invalid-this: ["error", { "capIsConstructor": false }]*/
diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md
index 137c588d9b15..c0683645628e 100644
--- a/docs/src/rules/no-irregular-whitespace.md
+++ b/docs/src/rules/no-irregular-whitespace.md
@@ -74,31 +74,31 @@ Examples of **incorrect** code for this rule with the default `{ "skipStrings":
```js
/*eslint no-irregular-whitespace: "error"*/
-function thing() /**/{
+var thing = function() /**/{
return 'test';
}
-function thing( /**/){
+var thing = function( /**/){
return 'test';
}
-function thing /**/(){
+var thing = function /**/(){
return 'test';
}
-function thing/**/(){
+var thing = function/**/(){
return 'test';
}
-function thing() {
+var thing = function() {
return 'test'; /**/
}
-function thing() {
+var thing = function() {
return 'test'; /**/
}
-function thing() {
+var thing = function() {
// Description : some descriptive text
}
@@ -106,12 +106,12 @@ function thing() {
Description : some descriptive text
*/
-function thing() {
+var thing = function() {
return / regexp/;
}
/*eslint-env es6*/
-function thing() {
+var thing = function() {
return `template string`;
}
```
@@ -125,15 +125,15 @@ Examples of **correct** code for this rule with the default `{ "skipStrings": tr
```js
/*eslint no-irregular-whitespace: "error"*/
-function thing() {
+var thing = function() {
return ' thing';
}
-function thing() {
+var thing = function() {
return 'thing';
}
-function thing() {
+var thing = function() {
return 'th ing';
}
```
diff --git a/docs/src/rules/no-nonoctal-decimal-escape.md b/docs/src/rules/no-nonoctal-decimal-escape.md
index 6ca59fdf79a4..43d0282635af 100644
--- a/docs/src/rules/no-nonoctal-decimal-escape.md
+++ b/docs/src/rules/no-nonoctal-decimal-escape.md
@@ -30,7 +30,7 @@ This rule disallows `\8` and `\9` escape sequences in string literals.
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-nonoctal-decimal-escape: "error"*/
@@ -52,7 +52,7 @@ var quux = "\0\8";
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-nonoctal-decimal-escape: "error"*/
diff --git a/docs/src/rules/no-octal-escape.md b/docs/src/rules/no-octal-escape.md
index ae8fecdce32c..755bc56bc84f 100644
--- a/docs/src/rules/no-octal-escape.md
+++ b/docs/src/rules/no-octal-escape.md
@@ -18,7 +18,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-octal-escape: "error"*/
@@ -30,7 +30,7 @@ var foo = "Copyright \251";
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-octal-escape: "error"*/
diff --git a/docs/src/rules/no-octal.md b/docs/src/rules/no-octal.md
index 6a8fa7709a4d..c40d9a977b23 100644
--- a/docs/src/rules/no-octal.md
+++ b/docs/src/rules/no-octal.md
@@ -21,7 +21,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-octal: "error"*/
@@ -34,7 +34,7 @@ var result = 5 + 07;
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-octal: "error"*/
diff --git a/docs/src/rules/no-param-reassign.md b/docs/src/rules/no-param-reassign.md
index 9c23c45f981a..a3cc558cf451 100644
--- a/docs/src/rules/no-param-reassign.md
+++ b/docs/src/rules/no-param-reassign.md
@@ -21,19 +21,19 @@ Examples of **incorrect** code for this rule:
```js
/*eslint no-param-reassign: "error"*/
-function foo(bar) {
+var foo = function(bar) {
bar = 13;
}
-function foo(bar) {
+var foo = function(bar) {
bar++;
}
-function foo(bar) {
+var foo = function(bar) {
for (bar in baz) {}
}
-function foo(bar) {
+var foo = function(bar) {
for (bar of baz) {}
}
```
@@ -47,7 +47,7 @@ Examples of **correct** code for this rule:
```js
/*eslint no-param-reassign: "error"*/
-function foo(bar) {
+var foo = function(bar) {
var baz = bar;
}
```
@@ -67,23 +67,23 @@ Examples of **correct** code for the default `{ "props": false }` option:
```js
/*eslint no-param-reassign: ["error", { "props": false }]*/
-function foo(bar) {
+var foo = function(bar) {
bar.prop = "value";
}
-function foo(bar) {
+var foo = function(bar) {
delete bar.aaa;
}
-function foo(bar) {
+var foo = function(bar) {
bar.aaa++;
}
-function foo(bar) {
+var foo = function(bar) {
for (bar.aaa in baz) {}
}
-function foo(bar) {
+var foo = function(bar) {
for (bar.aaa of baz) {}
}
```
@@ -97,23 +97,23 @@ Examples of **incorrect** code for the `{ "props": true }` option:
```js
/*eslint no-param-reassign: ["error", { "props": true }]*/
-function foo(bar) {
+var foo = function(bar) {
bar.prop = "value";
}
-function foo(bar) {
+var foo = function(bar) {
delete bar.aaa;
}
-function foo(bar) {
+var foo = function(bar) {
bar.aaa++;
}
-function foo(bar) {
+var foo = function(bar) {
for (bar.aaa in baz) {}
}
-function foo(bar) {
+var foo = function(bar) {
for (bar.aaa of baz) {}
}
```
@@ -127,23 +127,23 @@ Examples of **correct** code for the `{ "props": true }` option with `"ignorePro
```js
/*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsFor": ["bar"] }]*/
-function foo(bar) {
+var foo = function(bar) {
bar.prop = "value";
}
-function foo(bar) {
+var foo = function(bar) {
delete bar.aaa;
}
-function foo(bar) {
+var foo = function(bar) {
bar.aaa++;
}
-function foo(bar) {
+var foo = function(bar) {
for (bar.aaa in baz) {}
}
-function foo(bar) {
+var foo = function(bar) {
for (bar.aaa of baz) {}
}
```
@@ -157,23 +157,23 @@ Examples of **correct** code for the `{ "props": true }` option with `"ignorePro
```js
/*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsForRegex": ["^bar"] }]*/
-function foo(barVar) {
+var foo = function(barVar) {
barVar.prop = "value";
}
-function foo(barrito) {
+var foo = function(barrito) {
delete barrito.aaa;
}
-function foo(bar_) {
+var foo = function(bar_) {
bar_.aaa++;
}
-function foo(barBaz) {
+var foo = function(barBaz) {
for (barBaz.aaa in baz) {}
}
-function foo(barBaz) {
+var foo = function(barBaz) {
for (barBaz.aaa of baz) {}
}
```
diff --git a/docs/src/rules/no-plusplus.md b/docs/src/rules/no-plusplus.md
index ba787f1968fd..4f564b51cb90 100644
--- a/docs/src/rules/no-plusplus.md
+++ b/docs/src/rules/no-plusplus.md
@@ -43,7 +43,7 @@ var bar = 42;
bar--;
for (i = 0; i < l; i++) {
- return;
+ doSomething(i);
}
```
@@ -63,7 +63,7 @@ var bar = 42;
bar -= 1;
for (i = 0; i < l; i += 1) {
- return;
+ doSomething(i);
}
```
diff --git a/docs/src/rules/no-restricted-syntax.md b/docs/src/rules/no-restricted-syntax.md
index 25d419c149d7..a3f51cfef809 100644
--- a/docs/src/rules/no-restricted-syntax.md
+++ b/docs/src/rules/no-restricted-syntax.md
@@ -57,7 +57,7 @@ The string and object formats can be freely mixed in the configuration as needed
Examples of **incorrect** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/* eslint no-restricted-syntax: ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] */
@@ -75,7 +75,7 @@ foo in bar;
Examples of **correct** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options:
-::: correct
+::: correct { "sourceType": "script" }
```js
/* eslint no-restricted-syntax: ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] */
diff --git a/docs/src/rules/no-return-assign.md b/docs/src/rules/no-return-assign.md
index 89277580f98e..b134fe8a1821 100644
--- a/docs/src/rules/no-return-assign.md
+++ b/docs/src/rules/no-return-assign.md
@@ -43,7 +43,7 @@ function doSomething() {
return foo = bar + 2;
}
-function doSomething() {
+function doSomethingElse() {
return foo += 2;
}
@@ -51,7 +51,7 @@ const foo = (a, b) => a = b
const bar = (a, b, c) => (a = b, c == b)
-function doSomething() {
+function doSomethingMore() {
return foo = bar && foo > 0;
}
```
@@ -69,11 +69,11 @@ function doSomething() {
return foo == bar + 2;
}
-function doSomething() {
+function doSomethingElse() {
return foo === bar + 2;
}
-function doSomething() {
+function doSomethingMore() {
return (foo = bar + 2);
}
@@ -81,7 +81,7 @@ const foo = (a, b) => (a = b)
const bar = (a, b, c) => ((a = b), c == b)
-function doSomething() {
+function doAnotherThing() {
return (foo = bar) && foo > 0;
}
```
@@ -104,11 +104,11 @@ function doSomething() {
return foo = bar + 2;
}
-function doSomething() {
+function doSomethingElse() {
return foo += 2;
}
-function doSomething() {
+function doSomethingMore() {
return (foo = bar + 2);
}
```
@@ -126,7 +126,7 @@ function doSomething() {
return foo == bar + 2;
}
-function doSomething() {
+function doSomethingElse() {
return foo === bar + 2;
}
```
diff --git a/docs/src/rules/no-return-await.md b/docs/src/rules/no-return-await.md
index 80328c09d8d6..2ee5a8b3659c 100644
--- a/docs/src/rules/no-return-await.md
+++ b/docs/src/rules/no-return-await.md
@@ -38,23 +38,23 @@ Examples of **correct** code for this rule:
```js
/*eslint no-return-await: "error"*/
-async function foo() {
+async function foo1() {
return bar();
}
-async function foo() {
+async function foo2() {
await bar();
return;
}
// This is essentially the same as `return await bar();`, but the rule checks only `await` in `return` statements
-async function foo() {
+async function foo3() {
const x = await bar();
return x;
}
// In this example the `await` is necessary to be able to catch errors thrown from `bar()`
-async function foo() {
+async function foo4() {
try {
return await bar();
} catch (error) {}
diff --git a/docs/src/rules/no-sequences.md b/docs/src/rules/no-sequences.md
index cb2b99c50e1f..811dae491968 100644
--- a/docs/src/rules/no-sequences.md
+++ b/docs/src/rules/no-sequences.md
@@ -25,7 +25,7 @@ This rule forbids the use of the comma operator, with the following exceptions:
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-sequences: "error"*/
@@ -51,7 +51,7 @@ with (doSomething(), val) {}
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-sequences: "error"*/
@@ -119,7 +119,7 @@ This rule takes one option, an object, with the following properties:
Examples of **incorrect** code for this rule with the `{ "allowInParentheses": false }` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-sequences: ["error", { "allowInParentheses": false }]*/
diff --git a/docs/src/rules/no-shadow-restricted-names.md b/docs/src/rules/no-shadow-restricted-names.md
index 308772edaf36..7a5e1b0403d9 100644
--- a/docs/src/rules/no-shadow-restricted-names.md
+++ b/docs/src/rules/no-shadow-restricted-names.md
@@ -22,7 +22,7 @@ Then any code used within the same scope would not get the global `undefined`, b
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-shadow-restricted-names: "error"*/
@@ -40,7 +40,7 @@ try {} catch(eval){}
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-shadow-restricted-names: "error"*/
diff --git a/docs/src/rules/no-shadow.md b/docs/src/rules/no-shadow.md
index e4a081f0a309..9b9a27ee7d38 100644
--- a/docs/src/rules/no-shadow.md
+++ b/docs/src/rules/no-shadow.md
@@ -36,14 +36,14 @@ function b() {
var a = 10;
}
-var b = function () {
+var c = function () {
var a = 10;
}
-function b(a) {
+function d(a) {
a = 10;
}
-b(a);
+d(a);
if (true) {
let a = 5;
diff --git a/docs/src/rules/no-throw-literal.md b/docs/src/rules/no-throw-literal.md
index 621bb11a09c3..1ab939ff3a00 100644
--- a/docs/src/rules/no-throw-literal.md
+++ b/docs/src/rules/no-throw-literal.md
@@ -84,10 +84,10 @@ throw foo("error");
throw new String("error");
-var foo = {
+var baz = {
bar: "error"
};
-throw foo.bar;
+throw baz.bar;
```
:::
diff --git a/docs/src/rules/no-undefined.md b/docs/src/rules/no-undefined.md
index 9b0be204b2f9..10096533a646 100644
--- a/docs/src/rules/no-undefined.md
+++ b/docs/src/rules/no-undefined.md
@@ -54,7 +54,7 @@ if (foo === undefined) {
// ...
}
-function foo(undefined) {
+function baz(undefined) {
// ...
}
diff --git a/docs/src/rules/no-unsafe-optional-chaining.md b/docs/src/rules/no-unsafe-optional-chaining.md
index e4cc6f831664..57ad6de87805 100644
--- a/docs/src/rules/no-unsafe-optional-chaining.md
+++ b/docs/src/rules/no-unsafe-optional-chaining.md
@@ -32,7 +32,7 @@ This rule aims to detect some cases where the use of optional chaining doesn't p
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-unsafe-optional-chaining: "error"*/
diff --git a/docs/src/rules/no-useless-escape.md b/docs/src/rules/no-useless-escape.md
index cd28fda75a8f..ff173c229c2f 100644
--- a/docs/src/rules/no-useless-escape.md
+++ b/docs/src/rules/no-useless-escape.md
@@ -43,7 +43,7 @@ Examples of **incorrect** code for this rule:
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-useless-escape: "error"*/
diff --git a/docs/src/rules/no-useless-return.md b/docs/src/rules/no-useless-return.md
index 961f0aef5ca0..de7045347c2e 100644
--- a/docs/src/rules/no-useless-return.md
+++ b/docs/src/rules/no-useless-return.md
@@ -18,14 +18,14 @@ Examples of **incorrect** code for this rule:
```js
/* eslint no-useless-return: "error" */
-function foo() { return; }
+var foo = function() { return; }
-function foo() {
+var foo = function() {
doSomething();
return;
}
-function foo() {
+var foo = function() {
if (condition) {
bar();
return;
@@ -34,7 +34,7 @@ function foo() {
}
}
-function foo() {
+var foo = function() {
switch (bar) {
case 1:
doSomething();
@@ -55,13 +55,13 @@ Examples of **correct** code for this rule:
```js
/* eslint no-useless-return: "error" */
-function foo() { return 5; }
+var foo = function() { return 5; }
-function foo() {
+var foo = function() {
return doSomething();
}
-function foo() {
+var foo = function() {
if (condition) {
bar();
return;
@@ -71,7 +71,7 @@ function foo() {
qux();
}
-function foo() {
+var foo = function() {
switch (bar) {
case 1:
doSomething();
@@ -81,7 +81,7 @@ function foo() {
}
}
-function foo() {
+var foo = function() {
for (const foo of bar) {
return;
}
diff --git a/docs/src/rules/no-with.md b/docs/src/rules/no-with.md
index 4fc3f75841d2..c4e880f812d8 100644
--- a/docs/src/rules/no-with.md
+++ b/docs/src/rules/no-with.md
@@ -17,7 +17,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint no-with: "error"*/
@@ -31,7 +31,7 @@ with (point) {
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint no-with: "error"*/
diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md
index d9a50ed86715..9c41bcd81b21 100644
--- a/docs/src/rules/one-var.md
+++ b/docs/src/rules/one-var.md
@@ -74,21 +74,21 @@ Examples of **incorrect** code for this rule with the default `"always"` option:
```js
/*eslint one-var: ["error", "always"]*/
-function foo() {
+function foo1() {
var bar;
var baz;
let qux;
let norf;
}
-function foo(){
+function foo2(){
const bar = false;
const baz = true;
let qux;
let norf;
}
-function foo() {
+function foo3() {
var bar;
if (baz) {
@@ -125,21 +125,21 @@ Examples of **correct** code for this rule with the default `"always"` option:
```js
/*eslint one-var: ["error", "always"]*/
-function foo() {
+function foo1() {
var bar,
baz;
let qux,
norf;
}
-function foo(){
+function foo2(){
const bar = true,
baz = false;
let qux,
norf;
}
-function foo() {
+function foo3() {
var bar,
qux;
@@ -148,7 +148,7 @@ function foo() {
}
}
-function foo(){
+function foo4(){
let bar;
if (baz) {
@@ -230,12 +230,12 @@ Examples of **correct** code for this rule with the `"never"` option:
```js
/*eslint one-var: ["error", "never"]*/
-function foo() {
+function foo1() {
var bar;
var baz;
}
-function foo() {
+function foo2() {
var bar;
if (baz) {
@@ -243,7 +243,7 @@ function foo() {
}
}
-function foo() {
+function foo3() {
let bar;
if (baz) {
@@ -277,12 +277,12 @@ Examples of **incorrect** code for this rule with the `"consecutive"` option:
```js
/*eslint one-var: ["error", "consecutive"]*/
-function foo() {
+function foo1() {
var bar;
var baz;
}
-function foo(){
+function foo2(){
var bar = 1;
var baz = 2;
@@ -311,12 +311,12 @@ Examples of **correct** code for this rule with the `"consecutive"` option:
```js
/*eslint one-var: ["error", "consecutive"]*/
-function foo() {
+function foo1() {
var bar,
baz;
}
-function foo(){
+function foo2(){
var bar = 1,
baz = 2;
@@ -349,14 +349,14 @@ Examples of **incorrect** code for this rule with the `{ var: "always", let: "ne
/*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/
/*eslint-env es6*/
-function foo() {
+function foo1() {
var bar;
var baz;
let qux,
norf;
}
-function foo() {
+function foo2() {
const bar = 1,
baz = 2;
let qux,
@@ -374,14 +374,14 @@ Examples of **correct** code for this rule with the `{ var: "always", let: "neve
/*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/
/*eslint-env es6*/
-function foo() {
+function foo1() {
var bar,
baz;
let qux;
let norf;
}
-function foo() {
+function foo2() {
const bar = 1;
const baz = 2;
let qux;
@@ -472,7 +472,7 @@ Examples of **incorrect** code for this rule with the `{ var: "never", let: "con
/*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/
/*eslint-env es6*/
-function foo() {
+function foo1() {
let a,
b;
let c;
@@ -481,7 +481,7 @@ function foo() {
e;
}
-function foo() {
+function foo2() {
const a = 1,
b = 2;
const c = 3;
@@ -501,7 +501,7 @@ Examples of **correct** code for this rule with the `{ var: "never", let: "conse
/*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/
/*eslint-env es6*/
-function foo() {
+function foo1() {
let a,
b;
@@ -511,7 +511,7 @@ function foo() {
let f;
}
-function foo() {
+function foo2() {
const a = 1,
b = 2;
diff --git a/docs/src/rules/padding-line-between-statements.md b/docs/src/rules/padding-line-between-statements.md
index 1b16881c3eee..710a6116d636 100644
--- a/docs/src/rules/padding-line-between-statements.md
+++ b/docs/src/rules/padding-line-between-statements.md
@@ -120,13 +120,13 @@ Examples of **correct** code for the `[{ blankLine: "always", prev: "*", next: "
{ blankLine: "always", prev: "*", next: "return" }
]*/
-function foo() {
+function foo1() {
bar();
return;
}
-function foo() {
+function foo2() {
return;
}
```
@@ -148,17 +148,17 @@ Examples of **incorrect** code for the `[{ blankLine: "always", prev: ["const",
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}
]*/
-function foo() {
+function foo1() {
var a = 0;
bar();
}
-function foo() {
+function foo2() {
let a = 0;
bar();
}
-function foo() {
+function foo3() {
const a = 0;
bar();
}
@@ -184,21 +184,21 @@ Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "l
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}
]*/
-function foo() {
+function foo1() {
var a = 0;
var b = 0;
bar();
}
-function foo() {
+function foo2() {
let a = 0;
const b = 0;
bar();
}
-function foo() {
+function foo3() {
const a = 0;
const b = 0;
diff --git a/docs/src/rules/prefer-reflect.md b/docs/src/rules/prefer-reflect.md
index e2a632eb6c66..d6e3d87330a1 100644
--- a/docs/src/rules/prefer-reflect.md
+++ b/docs/src/rules/prefer-reflect.md
@@ -380,7 +380,7 @@ delete foo.bar; // deleting object property
Examples of **correct** code for this rule when used without exceptions:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint prefer-reflect: "error"*/
@@ -395,7 +395,7 @@ Note: For a rule preventing deletion of variables, see [no-delete-var instead](n
Examples of **correct** code for this rule with the `{ "exceptions": ["delete"] }` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint prefer-reflect: ["error", { "exceptions": ["delete"] }]*/
diff --git a/docs/src/rules/prefer-rest-params.md b/docs/src/rules/prefer-rest-params.md
index cf234260290d..4406b5468dcd 100644
--- a/docs/src/rules/prefer-rest-params.md
+++ b/docs/src/rules/prefer-rest-params.md
@@ -19,7 +19,7 @@ This rule is aimed to flag usage of `arguments` variables.
Examples of **incorrect** code for this rule:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint prefer-rest-params: "error"*/
@@ -43,7 +43,7 @@ function foo(action) {
Examples of **correct** code for this rule:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint prefer-rest-params: "error"*/
diff --git a/docs/src/rules/require-await.md b/docs/src/rules/require-await.md
index a17eb972dcad..e0371dfb6ade 100644
--- a/docs/src/rules/require-await.md
+++ b/docs/src/rules/require-await.md
@@ -63,7 +63,7 @@ bar(async () => {
await doSomething();
});
-function foo() {
+function baz() {
doSomething();
}
diff --git a/docs/src/rules/require-jsdoc.md b/docs/src/rules/require-jsdoc.md
index 20516fb01883..82ea4b432222 100644
--- a/docs/src/rules/require-jsdoc.md
+++ b/docs/src/rules/require-jsdoc.md
@@ -77,7 +77,7 @@ function foo() {
return 10;
}
-var foo = () => {
+var bar = () => {
return 10;
};
@@ -87,11 +87,11 @@ class Foo {
}
}
-var foo = function() {
+var bar = function() {
return 10;
};
-var foo = {
+var bar = {
bar: function() {
return 10;
},
@@ -131,21 +131,21 @@ function foo() {
* @params {int} test - some number
* @returns {int} sum of test and 10
*/
-var foo = (test) => {
+var bar = (test) => {
return test + 10;
}
/**
* It returns 10
*/
-var foo = () => {
+var bar = () => {
return 10;
}
/**
* It returns 10
*/
-var foo = function() {
+var bar = function() {
return 10;
}
@@ -169,11 +169,11 @@ class Foo {
/**
* It returns 10
*/
-var foo = function() {
+var bar = function() {
return 10;
};
-var foo = {
+var bar = {
/**
* It returns 10
*/
diff --git a/docs/src/rules/require-yield.md b/docs/src/rules/require-yield.md
index e4f975faa3e4..eec9d0cfc941 100644
--- a/docs/src/rules/require-yield.md
+++ b/docs/src/rules/require-yield.md
@@ -41,12 +41,12 @@ function* foo() {
return 10;
}
-function foo() {
+function bar() {
return 10;
}
// This rule does not warn on empty generator functions.
-function* foo() { }
+function* baz() { }
```
:::
diff --git a/docs/src/rules/space-before-function-paren.md b/docs/src/rules/space-before-function-paren.md
index 1948d903cf0e..a132105ac415 100644
--- a/docs/src/rules/space-before-function-paren.md
+++ b/docs/src/rules/space-before-function-paren.md
@@ -85,13 +85,13 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
};
-var foo = async() => 1
+var baz = async() => 1
```
:::
@@ -122,13 +122,13 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
};
-var foo = async () => 1
+var baz = async () => 1
```
:::
@@ -161,13 +161,13 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
};
-var foo = async () => 1
+var baz = async () => 1
```
:::
@@ -198,13 +198,13 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
};
-var foo = async() => 1
+var baz = async() => 1
```
:::
@@ -233,13 +233,13 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
};
-var foo = async(a) => await a
+var baz = async(a) => await a
```
:::
@@ -266,13 +266,13 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
};
-var foo = async (a) => await a
+var baz = async (a) => await a
```
:::
@@ -301,7 +301,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
@@ -332,7 +332,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
@@ -361,7 +361,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
@@ -396,7 +396,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
diff --git a/docs/src/rules/space-before-function-parentheses.md b/docs/src/rules/space-before-function-parentheses.md
index a5c0eceb01fa..0b6b9b60d610 100644
--- a/docs/src/rules/space-before-function-parentheses.md
+++ b/docs/src/rules/space-before-function-parentheses.md
@@ -59,7 +59,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
@@ -93,7 +93,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
@@ -127,7 +127,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
@@ -161,7 +161,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
@@ -191,7 +191,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
@@ -221,7 +221,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
@@ -251,7 +251,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar() {
// ...
}
@@ -281,7 +281,7 @@ class Foo {
}
}
-var foo = {
+var baz = {
bar () {
// ...
}
diff --git a/docs/src/rules/strict.md b/docs/src/rules/strict.md
index c559b00710d4..731ddba2c20c 100644
--- a/docs/src/rules/strict.md
+++ b/docs/src/rules/strict.md
@@ -82,7 +82,7 @@ Otherwise the `"safe"` option corresponds to the `"function"` option. Note that
Examples of **incorrect** code for this rule with the `"global"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "global"]*/
@@ -93,7 +93,7 @@ function foo() {
:::
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "global"]*/
@@ -105,7 +105,7 @@ function foo() {
:::
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "global"]*/
@@ -121,7 +121,7 @@ function foo() {
Examples of **correct** code for this rule with the `"global"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint strict: ["error", "global"]*/
@@ -140,7 +140,7 @@ This option ensures that all function bodies are strict mode code, while global
Examples of **incorrect** code for this rule with the `"function"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "function"]*/
@@ -153,7 +153,7 @@ function foo() {
:::
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "function"]*/
@@ -170,7 +170,7 @@ function foo() {
:::
-::: incorrect
+::: incorrect { "ecmaVersion": 6, "sourceType": "script" }
```js
/*eslint strict: ["error", "function"]*/
@@ -192,7 +192,7 @@ function foo(a = 1) {
Examples of **correct** code for this rule with the `"function"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint strict: ["error", "function"]*/
@@ -225,7 +225,7 @@ var foo = (function() {
Examples of **incorrect** code for this rule with the `"never"` option:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "never"]*/
@@ -238,7 +238,7 @@ function foo() {
:::
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
/*eslint strict: ["error", "never"]*/
@@ -252,7 +252,7 @@ function foo() {
Examples of **correct** code for this rule with the `"never"` option:
-::: correct
+::: correct { "sourceType": "script" }
```js
/*eslint strict: ["error", "never"]*/
@@ -271,7 +271,7 @@ This option ensures that all functions are executed in strict mode. A strict mod
Examples of **incorrect** code for this rule with the earlier default option which has been removed:
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
// "strict": "error"
@@ -282,7 +282,7 @@ function foo() {
:::
-::: incorrect
+::: incorrect { "sourceType": "script" }
```js
// "strict": "error"
@@ -298,7 +298,7 @@ function foo() {
Examples of **correct** code for this rule with the earlier default option which has been removed:
-::: correct
+::: correct { "sourceType": "script" }
```js
// "strict": "error"
@@ -311,7 +311,7 @@ function foo() {
:::
-::: correct
+::: correct { "sourceType": "script" }
```js
// "strict": "error"
@@ -323,7 +323,7 @@ function foo() {
:::
-::: correct
+::: correct { "sourceType": "script" }
```js
// "strict": "error"
diff --git a/docs/src/rules/vars-on-top.md b/docs/src/rules/vars-on-top.md
index 20d1440aee4a..5fdb2f160489 100644
--- a/docs/src/rules/vars-on-top.md
+++ b/docs/src/rules/vars-on-top.md
@@ -34,7 +34,7 @@ function doSomething() {
}
// Variable declaration in for initializer:
-function doSomething() {
+function doSomethingElse() {
for (var i=0; i<10; i++) {}
}
```
@@ -95,7 +95,7 @@ function doSomething() {
}
}
-function doSomething() {
+function doSomethingElse() {
var i;
for (i=0; i<10; i++) {}
}
diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md
index d88e35cfa04c..da4faf70ebae 100644
--- a/docs/src/use/command-line-interface.md
+++ b/docs/src/use/command-line-interface.md
@@ -110,6 +110,7 @@ Miscellaneous:
--env-info Output execution environment information - default: false
--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*
--debug Output debugging information
-h, --help Show help
-v, --version Output the version number
@@ -703,6 +704,18 @@ This option causes ESLint to exit with exit code 2 if one or more fatal parsing
npx eslint --exit-on-fatal-error file.js
```
+#### `--no-warn-ignored`
+
+**Flat Config Mode Only.** This option suppresses both `File ignored by default` and `File ignored because of a matching ignore pattern` warnings when an ignored filename is passed explicitly. It is useful when paired with `--max-warnings 0` as it will prevent exit code 1 due to the aforementioned warning.
+
+* **Argument Type**: No argument.
+
+##### `--no-warn-ignored` example
+
+```shell
+npx eslint --no-warn-ignored --max-warnings 0 ignored-file.js
+```
+
#### `--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.
diff --git a/docs/src/use/configure/ignore.md b/docs/src/use/configure/ignore.md
index ffc23428ee02..16f1bfbcdc92 100644
--- a/docs/src/use/configure/ignore.md
+++ b/docs/src/use/configure/ignore.md
@@ -149,7 +149,7 @@ You'll see this warning:
```text
foo.js
- 0:0 warning File ignored because of a matching ignore pattern. Use "--no-ignore" to override.
+ 0:0 warning File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.
✖ 1 problem (0 errors, 1 warning)
```
diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html
index 9545c0914c4b..2981958fab76 100644
--- a/docs/src/use/formatters/html-formatter-example.html
+++ b/docs/src/use/formatters/html-formatter-example.html
@@ -118,7 +118,7 @@
ESLint Report
- 9 problems (5 errors, 4 warnings) - Generated on Fri Sep 22 2023 17:03:30 GMT-0400 (Eastern Daylight Time)
+ 9 problems (5 errors, 4 warnings) - Generated on Fri Oct 06 2023 16:14:35 GMT-0400 (Eastern Daylight Time)
diff --git a/lib/cli.js b/lib/cli.js
index a14930e9b0f2..807d28a0d1bc 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -91,7 +91,8 @@ async function translateOptions({
reportUnusedDisableDirectives,
resolvePluginsRelativeTo,
rule,
- rulesdir
+ rulesdir,
+ warnIgnored
}, configType) {
let overrideConfig, overrideConfigFile;
@@ -182,6 +183,7 @@ async function translateOptions({
if (configType === "flat") {
options.ignorePatterns = ignorePattern;
+ options.warnIgnored = warnIgnored;
} else {
options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
options.rulePaths = rulesdir;
@@ -385,7 +387,9 @@ const cli = {
if (useStdin) {
results = await engine.lintText(text, {
filePath: options.stdinFilename,
- warnIgnored: true
+
+ // flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility
+ warnIgnored: usingFlatConfig ? void 0 : true
});
} else {
results = await engine.lintFiles(files);
diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js
index a79c02d663b5..df850995d87f 100644
--- a/lib/config/flat-config-schema.js
+++ b/lib/config/flat-config-schema.js
@@ -179,9 +179,7 @@ class InvalidRuleSeverityError extends Error {
* @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity.
*/
function assertIsRuleSeverity(ruleId, value) {
- const severity = typeof value === "string"
- ? ruleSeverities.get(value.toLowerCase())
- : ruleSeverities.get(value);
+ const severity = ruleSeverities.get(value);
if (typeof severity === "undefined") {
throw new InvalidRuleSeverityError(ruleId, value);
diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js
index e25b10e8bc4e..72828363c3da 100644
--- a/lib/eslint/eslint-helpers.js
+++ b/lib/eslint/eslint-helpers.js
@@ -594,9 +594,9 @@ function createIgnoreResult(filePath, baseDir) {
const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
if (isInNodeModules) {
- message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.";
+ message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
} else {
- message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
+ message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
}
return {
@@ -676,6 +676,7 @@ function processOptions({
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.
+ warnIgnored = true,
...unknownOptions
}) {
const errors = [];
@@ -781,6 +782,9 @@ function processOptions({
) {
errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
}
+ if (typeof warnIgnored !== "boolean") {
+ errors.push("'warnIgnored' must be a boolean.");
+ }
if (errors.length > 0) {
throw new ESLintInvalidOptionsError(errors);
}
@@ -802,7 +806,8 @@ function processOptions({
globInputPaths,
ignore,
ignorePatterns,
- reportUnusedDisableDirectives
+ reportUnusedDisableDirectives,
+ warnIgnored
};
}
diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js
index 4ef386113616..306c80de1d65 100644
--- a/lib/eslint/flat-eslint.js
+++ b/lib/eslint/flat-eslint.js
@@ -84,6 +84,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
* when a string.
* @property {Record} [plugins] An array of plugin implementations.
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
+ * @property {boolean} warnIgnored Show warnings when the file list includes ignored files
*/
//------------------------------------------------------------------------------
@@ -749,7 +750,8 @@ class FlatESLint {
fixTypes,
reportUnusedDisableDirectives,
globInputPaths,
- errorOnUnmatchedPattern
+ errorOnUnmatchedPattern,
+ warnIgnored
} = eslintOptions;
const startTime = Date.now();
const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
@@ -795,7 +797,11 @@ class FlatESLint {
* pattern, then notify the user.
*/
if (ignored) {
- return createIgnoreResult(filePath, cwd);
+ if (warnIgnored) {
+ return createIgnoreResult(filePath, cwd);
+ }
+
+ return void 0;
}
const config = configs.getConfig(filePath);
@@ -908,7 +914,7 @@ class FlatESLint {
const {
filePath,
- warnIgnored = false,
+ warnIgnored,
...unknownOptions
} = options || {};
@@ -922,7 +928,7 @@ class FlatESLint {
throw new Error("'options.filePath' must be a non-empty string or undefined");
}
- if (typeof warnIgnored !== "boolean") {
+ if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") {
throw new Error("'options.warnIgnored' must be a boolean or undefined");
}
@@ -937,7 +943,8 @@ class FlatESLint {
allowInlineConfig,
cwd,
fix,
- reportUnusedDisableDirectives
+ reportUnusedDisableDirectives,
+ warnIgnored: constructorWarnIgnored
} = eslintOptions;
const results = [];
const startTime = Date.now();
@@ -945,7 +952,9 @@ class FlatESLint {
// Clear the last used config arrays.
if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) {
- if (warnIgnored) {
+ const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored;
+
+ if (shouldWarnIgnored) {
results.push(createIgnoreResult(resolvedFilename, cwd));
}
} else {
diff --git a/lib/linter/apply-disable-directives.js b/lib/linter/apply-disable-directives.js
index 13ced990ff48..55f7683f3f53 100644
--- a/lib/linter/apply-disable-directives.js
+++ b/lib/linter/apply-disable-directives.js
@@ -87,7 +87,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
return directives.map(directive => {
const { ruleId } = directive;
- const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u");
+ const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?['"]?)${escapeRegExp(ruleId)}\k(?:\s*,\s*|$)`, "u");
const match = regex.exec(listText);
const matchedText = match[0];
const matchStartOffset = listStartOffset + match.index;
diff --git a/lib/linter/code-path-analysis/code-path-state.js b/lib/linter/code-path-analysis/code-path-state.js
index d187297d32b0..2b0dc2bfca04 100644
--- a/lib/linter/code-path-analysis/code-path-state.js
+++ b/lib/linter/code-path-analysis/code-path-state.js
@@ -12,13 +12,622 @@
const CodePathSegment = require("./code-path-segment"),
ForkContext = require("./fork-context");
+//-----------------------------------------------------------------------------
+// Contexts
+//-----------------------------------------------------------------------------
+
+/**
+ * Represents the context in which a `break` statement can be used.
+ *
+ * A `break` statement without a label is only valid in a few places in
+ * JavaScript: any type of loop or a `switch` statement. Otherwise, `break`
+ * without a label causes a syntax error. For these contexts, `breakable` is
+ * set to `true` to indicate that a `break` without a label is valid.
+ *
+ * However, a `break` statement with a label is also valid inside of a labeled
+ * statement. For example, this is valid:
+ *
+ * a : {
+ * break a;
+ * }
+ *
+ * The `breakable` property is set false for labeled statements to indicate
+ * that `break` without a label is invalid.
+ */
+class BreakContext {
+
+ /**
+ * Creates a new instance.
+ * @param {BreakContext} upperContext The previous `BreakContext`.
+ * @param {boolean} breakable Indicates if we are inside a statement where
+ * `break` without a label will exit the statement.
+ * @param {string|null} label The label for the statement.
+ * @param {ForkContext} forkContext The current fork context.
+ */
+ constructor(upperContext, breakable, label, forkContext) {
+
+ /**
+ * The previous `BreakContext`
+ * @type {BreakContext}
+ */
+ this.upper = upperContext;
+
+ /**
+ * Indicates if we are inside a statement where `break` without a label
+ * will exit the statement.
+ * @type {boolean}
+ */
+ this.breakable = breakable;
+
+ /**
+ * The label associated with the statement.
+ * @type {string|null}
+ */
+ this.label = label;
+
+ /**
+ * The fork context for the `break`.
+ * @type {ForkContext}
+ */
+ this.brokenForkContext = ForkContext.newEmpty(forkContext);
+ }
+}
+
+/**
+ * Represents the context for `ChainExpression` nodes.
+ */
+class ChainContext {
+
+ /**
+ * Creates a new instance.
+ * @param {ChainContext} upperContext The previous `ChainContext`.
+ */
+ constructor(upperContext) {
+
+ /**
+ * The previous `ChainContext`
+ * @type {ChainContext}
+ */
+ this.upper = upperContext;
+
+ /**
+ * The number of choice contexts inside of the `ChainContext`.
+ * @type {number}
+ */
+ this.choiceContextCount = 0;
+
+ }
+}
+
+/**
+ * Represents a choice in the code path.
+ *
+ * Choices are created by logical operators such as `&&`, loops, conditionals,
+ * and `if` statements. This is the point at which the code path has a choice of
+ * which direction to go.
+ *
+ * The result of a choice might be in the left (test) expression of another choice,
+ * and in that case, may create a new fork. For example, `a || b` is a choice
+ * but does not create a new fork because the result of the expression is
+ * not used as the test expression in another expression. In this case,
+ * `isForkingAsResult` is false. In the expression `a || b || c`, the `a || b`
+ * expression appears as the test expression for `|| c`, so the
+ * result of `a || b` creates a fork because execution may or may not
+ * continue to `|| c`. `isForkingAsResult` for `a || b` in this case is true
+ * while `isForkingAsResult` for `|| c` is false. (`isForkingAsResult` is always
+ * false for `if` statements, conditional expressions, and loops.)
+ *
+ * All of the choices except one (`??`) operate on a true/false fork, meaning if
+ * true go one way and if false go the other (tracked by `trueForkContext` and
+ * `falseForkContext`). The `??` operator doesn't operate on true/false because
+ * the left expression is evaluated to be nullish or not, so only if nullish do
+ * we fork to the right expression (tracked by `nullishForkContext`).
+ */
+class ChoiceContext {
+
+ /**
+ * Creates a new instance.
+ * @param {ChoiceContext} upperContext The previous `ChoiceContext`.
+ * @param {string} kind The kind of choice. If it's a logical or assignment expression, this
+ * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
+ * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
+ * @param {boolean} isForkingAsResult Indicates if the result of the choice
+ * creates a fork.
+ * @param {ForkContext} forkContext The containing `ForkContext`.
+ */
+ constructor(upperContext, kind, isForkingAsResult, forkContext) {
+
+ /**
+ * The previous `ChoiceContext`
+ * @type {ChoiceContext}
+ */
+ this.upper = upperContext;
+
+ /**
+ * The kind of choice. If it's a logical or assignment expression, this
+ * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
+ * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
+ * @type {string}
+ */
+ this.kind = kind;
+
+ /**
+ * Indicates if the result of the choice forks the code path.
+ * @type {boolean}
+ */
+ this.isForkingAsResult = isForkingAsResult;
+
+ /**
+ * The fork context for the `true` path of the choice.
+ * @type {ForkContext}
+ */
+ this.trueForkContext = ForkContext.newEmpty(forkContext);
+
+ /**
+ * The fork context for the `false` path of the choice.
+ * @type {ForkContext}
+ */
+ this.falseForkContext = ForkContext.newEmpty(forkContext);
+
+ /**
+ * The fork context for when the choice result is `null` or `undefined`.
+ * @type {ForkContext}
+ */
+ this.nullishForkContext = ForkContext.newEmpty(forkContext);
+
+ /**
+ * Indicates if any of `trueForkContext`, `falseForkContext`, or
+ * `nullishForkContext` have been updated with segments from a child context.
+ * @type {boolean}
+ */
+ this.processed = false;
+ }
+
+}
+
+/**
+ * Base class for all loop contexts.
+ */
+class LoopContextBase {
+
+ /**
+ * Creates a new instance.
+ * @param {LoopContext|null} upperContext The previous `LoopContext`.
+ * @param {string} type The AST node's `type` for the loop.
+ * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+ * @param {BreakContext} breakContext The context for breaking the loop.
+ */
+ constructor(upperContext, type, label, breakContext) {
+
+ /**
+ * The previous `LoopContext`.
+ * @type {LoopContext}
+ */
+ this.upper = upperContext;
+
+ /**
+ * The AST node's `type` for the loop.
+ * @type {string}
+ */
+ this.type = type;
+
+ /**
+ * The label for the loop from an enclosing `LabeledStatement`.
+ * @type {string|null}
+ */
+ this.label = label;
+
+ /**
+ * The fork context for when `break` is encountered.
+ * @type {ForkContext}
+ */
+ this.brokenForkContext = breakContext.brokenForkContext;
+ }
+}
+
+/**
+ * Represents the context for a `while` loop.
+ */
+class WhileLoopContext extends LoopContextBase {
+
+ /**
+ * Creates a new instance.
+ * @param {LoopContext|null} upperContext The previous `LoopContext`.
+ * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+ * @param {BreakContext} breakContext The context for breaking the loop.
+ */
+ constructor(upperContext, label, breakContext) {
+ super(upperContext, "WhileStatement", label, breakContext);
+
+ /**
+ * The hardcoded literal boolean test condition for
+ * the loop. Used to catch infinite or skipped loops.
+ * @type {boolean|undefined}
+ */
+ this.test = void 0;
+
+ /**
+ * The segments representing the test condition where `continue` will
+ * jump to. The test condition will typically have just one segment but
+ * it's possible for there to be more than one.
+ * @type {Array|null}
+ */
+ this.continueDestSegments = null;
+ }
+}
+
+/**
+ * Represents the context for a `do-while` loop.
+ */
+class DoWhileLoopContext extends LoopContextBase {
+
+ /**
+ * Creates a new instance.
+ * @param {LoopContext|null} upperContext The previous `LoopContext`.
+ * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+ * @param {BreakContext} breakContext The context for breaking the loop.
+ * @param {ForkContext} forkContext The enclosing fork context.
+ */
+ constructor(upperContext, label, breakContext, forkContext) {
+ super(upperContext, "DoWhileStatement", label, breakContext);
+
+ /**
+ * The hardcoded literal boolean test condition for
+ * the loop. Used to catch infinite or skipped loops.
+ * @type {boolean|undefined}
+ */
+ this.test = void 0;
+
+ /**
+ * The segments at the start of the loop body. This is the only loop
+ * where the test comes at the end, so the first iteration always
+ * happens and we need a reference to the first statements.
+ * @type {Array|null}
+ */
+ this.entrySegments = null;
+
+ /**
+ * The fork context to follow when a `continue` is found.
+ * @type {ForkContext}
+ */
+ this.continueForkContext = ForkContext.newEmpty(forkContext);
+ }
+}
+
+/**
+ * Represents the context for a `for` loop.
+ */
+class ForLoopContext extends LoopContextBase {
+
+ /**
+ * Creates a new instance.
+ * @param {LoopContext|null} upperContext The previous `LoopContext`.
+ * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+ * @param {BreakContext} breakContext The context for breaking the loop.
+ */
+ constructor(upperContext, label, breakContext) {
+ super(upperContext, "ForStatement", label, breakContext);
+
+ /**
+ * The hardcoded literal boolean test condition for
+ * the loop. Used to catch infinite or skipped loops.
+ * @type {boolean|undefined}
+ */
+ this.test = void 0;
+
+ /**
+ * The end of the init expression. This may change during the lifetime
+ * of the instance as we traverse the loop because some loops don't have
+ * an init expression.
+ * @type {Array|null}
+ */
+ this.endOfInitSegments = null;
+
+ /**
+ * The start of the test expression. This may change during the lifetime
+ * of the instance as we traverse the loop because some loops don't have
+ * a test expression.
+ * @type {Array|null}
+ */
+ this.testSegments = null;
+
+ /**
+ * The end of the test expression. This may change during the lifetime
+ * of the instance as we traverse the loop because some loops don't have
+ * a test expression.
+ * @type {Array|null}
+ */
+ this.endOfTestSegments = null;
+
+ /**
+ * The start of the update expression. This may change during the lifetime
+ * of the instance as we traverse the loop because some loops don't have
+ * an update expression.
+ * @type {Array|null}
+ */
+ this.updateSegments = null;
+
+ /**
+ * The end of the update expresion. This may change during the lifetime
+ * of the instance as we traverse the loop because some loops don't have
+ * an update expression.
+ * @type {Array|null}
+ */
+ this.endOfUpdateSegments = null;
+
+ /**
+ * The segments representing the test condition where `continue` will
+ * jump to. The test condition will typically have just one segment but
+ * it's possible for there to be more than one. This may change during the
+ * lifetime of the instance as we traverse the loop because some loops
+ * don't have an update expression. When there is an update expression, this
+ * will end up pointing to that expression; otherwise it will end up pointing
+ * to the test expression.
+ * @type {Array|null}
+ */
+ this.continueDestSegments = null;
+ }
+}
+
+/**
+ * Represents the context for a `for-in` loop.
+ *
+ * Terminology:
+ * - "left" means the part of the loop to the left of the `in` keyword. For
+ * example, in `for (var x in y)`, the left is `var x`.
+ * - "right" means the part of the loop to the right of the `in` keyword. For
+ * example, in `for (var x in y)`, the right is `y`.
+ */
+class ForInLoopContext extends LoopContextBase {
+
+ /**
+ * Creates a new instance.
+ * @param {LoopContext|null} upperContext The previous `LoopContext`.
+ * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+ * @param {BreakContext} breakContext The context for breaking the loop.
+ */
+ constructor(upperContext, label, breakContext) {
+ super(upperContext, "ForInStatement", label, breakContext);
+
+ /**
+ * The segments that came immediately before the start of the loop.
+ * This allows you to traverse backwards out of the loop into the
+ * surrounding code. This is necessary to evaluate the right expression
+ * correctly, as it must be evaluated in the same way as the left
+ * expression, but the pointer to these segments would otherwise be
+ * lost if not stored on the instance. Once the right expression has
+ * been evaluated, this property is no longer used.
+ * @type {Array|null}
+ */
+ this.prevSegments = null;
+
+ /**
+ * Segments representing the start of everything to the left of the
+ * `in` keyword. This can be used to move forward towards
+ * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
+ * effectively the head and tail of a doubly-linked list.
+ * @type {Array|null}
+ */
+ this.leftSegments = null;
+
+ /**
+ * Segments representing the end of everything to the left of the
+ * `in` keyword. This can be used to move backward towards `leftSegments`.
+ * `leftSegments` and `endOfLeftSegments` are effectively the head
+ * and tail of a doubly-linked list.
+ * @type {Array|null}
+ */
+ this.endOfLeftSegments = null;
+
+ /**
+ * The segments representing the left expression where `continue` will
+ * jump to. In `for-in` loops, `continue` must always re-execute the
+ * left expression each time through the loop. This contains the same
+ * segments as `leftSegments`, but is duplicated here so each loop
+ * context has the same property pointing to where `continue` should
+ * end up.
+ * @type {Array|null}
+ */
+ this.continueDestSegments = null;
+ }
+}
+
+/**
+ * Represents the context for a `for-of` loop.
+ */
+class ForOfLoopContext extends LoopContextBase {
+
+ /**
+ * Creates a new instance.
+ * @param {LoopContext|null} upperContext The previous `LoopContext`.
+ * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+ * @param {BreakContext} breakContext The context for breaking the loop.
+ */
+ constructor(upperContext, label, breakContext) {
+ super(upperContext, "ForOfStatement", label, breakContext);
+
+ /**
+ * The segments that came immediately before the start of the loop.
+ * This allows you to traverse backwards out of the loop into the
+ * surrounding code. This is necessary to evaluate the right expression
+ * correctly, as it must be evaluated in the same way as the left
+ * expression, but the pointer to these segments would otherwise be
+ * lost if not stored on the instance. Once the right expression has
+ * been evaluated, this property is no longer used.
+ * @type {Array|null}
+ */
+ this.prevSegments = null;
+
+ /**
+ * Segments representing the start of everything to the left of the
+ * `of` keyword. This can be used to move forward towards
+ * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
+ * effectively the head and tail of a doubly-linked list.
+ * @type {Array|null}
+ */
+ this.leftSegments = null;
+
+ /**
+ * Segments representing the end of everything to the left of the
+ * `of` keyword. This can be used to move backward towards `leftSegments`.
+ * `leftSegments` and `endOfLeftSegments` are effectively the head
+ * and tail of a doubly-linked list.
+ * @type {Array|null}
+ */
+ this.endOfLeftSegments = null;
+
+ /**
+ * The segments representing the left expression where `continue` will
+ * jump to. In `for-in` loops, `continue` must always re-execute the
+ * left expression each time through the loop. This contains the same
+ * segments as `leftSegments`, but is duplicated here so each loop
+ * context has the same property pointing to where `continue` should
+ * end up.
+ * @type {Array|null}
+ */
+ this.continueDestSegments = null;
+ }
+}
+
+/**
+ * Represents the context for any loop.
+ * @typedef {WhileLoopContext|DoWhileLoopContext|ForLoopContext|ForInLoopContext|ForOfLoopContext} LoopContext
+ */
+
+/**
+ * Represents the context for a `switch` statement.
+ */
+class SwitchContext {
+
+ /**
+ * Creates a new instance.
+ * @param {SwitchContext} upperContext The previous context.
+ * @param {boolean} hasCase Indicates if there is at least one `case` statement.
+ * `default` doesn't count.
+ */
+ constructor(upperContext, hasCase) {
+
+ /**
+ * The previous context.
+ * @type {SwitchContext}
+ */
+ this.upper = upperContext;
+
+ /**
+ * Indicates if there is at least one `case` statement. `default` doesn't count.
+ * @type {boolean}
+ */
+ this.hasCase = hasCase;
+
+ /**
+ * The `default` keyword.
+ * @type {Array|null}
+ */
+ this.defaultSegments = null;
+
+ /**
+ * The default case body starting segments.
+ * @type {Array|null}
+ */
+ this.defaultBodySegments = null;
+
+ /**
+ * Indicates if a `default` case and is empty exists.
+ * @type {boolean}
+ */
+ this.foundEmptyDefault = false;
+
+ /**
+ * Indicates that a `default` exists and is the last case.
+ * @type {boolean}
+ */
+ this.lastIsDefault = false;
+
+ /**
+ * The number of fork contexts created. This is equivalent to the
+ * number of `case` statements plus a `default` statement (if present).
+ * @type {number}
+ */
+ this.forkCount = 0;
+ }
+}
+
+/**
+ * Represents the context for a `try` statement.
+ */
+class TryContext {
+
+ /**
+ * Creates a new instance.
+ * @param {TryContext} upperContext The previous context.
+ * @param {boolean} hasFinalizer Indicates if the `try` statement has a
+ * `finally` block.
+ * @param {ForkContext} forkContext The enclosing fork context.
+ */
+ constructor(upperContext, hasFinalizer, forkContext) {
+
+ /**
+ * The previous context.
+ * @type {TryContext}
+ */
+ this.upper = upperContext;
+
+ /**
+ * Indicates if the `try` statement has a `finally` block.
+ * @type {boolean}
+ */
+ this.hasFinalizer = hasFinalizer;
+
+ /**
+ * Tracks the traversal position inside of the `try` statement. This is
+ * used to help determine the context necessary to create paths because
+ * a `try` statement may or may not have `catch` or `finally` blocks,
+ * and code paths behave differently in those blocks.
+ * @type {"try"|"catch"|"finally"}
+ */
+ this.position = "try";
+
+ /**
+ * If the `try` statement has a `finally` block, this affects how a
+ * `return` statement behaves in the `try` block. Without `finally`,
+ * `return` behaves as usual and doesn't require a fork; with `finally`,
+ * `return` forks into the `finally` block, so we need a fork context
+ * to track it.
+ * @type {ForkContext|null}
+ */
+ this.returnedForkContext = hasFinalizer
+ ? ForkContext.newEmpty(forkContext)
+ : null;
+
+ /**
+ * When a `throw` occurs inside of a `try` block, the code path forks
+ * into the `catch` or `finally` blocks, and this fork context tracks
+ * that path.
+ * @type {ForkContext}
+ */
+ this.thrownForkContext = ForkContext.newEmpty(forkContext);
+
+ /**
+ * Indicates if the last segment in the `try` block is reachable.
+ * @type {boolean}
+ */
+ this.lastOfTryIsReachable = false;
+
+ /**
+ * Indicates if the last segment in the `catch` block is reachable.
+ * @type {boolean}
+ */
+ this.lastOfCatchIsReachable = false;
+ }
+}
+
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Adds given segments into the `dest` array.
- * If the `others` array does not includes the given segments, adds to the `all`
+ * If the `others` array does not include the given segments, adds to the `all`
* array as well.
*
* This adds only reachable and used segments.
@@ -40,9 +649,9 @@ function addToReturnedOrThrown(dest, others, all, segments) {
}
/**
- * Gets a loop-context for a `continue` statement.
- * @param {CodePathState} state A state to get.
- * @param {string} label The label of a `continue` statement.
+ * Gets a loop context for a `continue` statement based on a given label.
+ * @param {CodePathState} state The state to search within.
+ * @param {string|null} label The label of a `continue` statement.
* @returns {LoopContext} A loop-context for a `continue` statement.
*/
function getContinueContext(state, label) {
@@ -65,9 +674,9 @@ function getContinueContext(state, label) {
/**
* Gets a context for a `break` statement.
- * @param {CodePathState} state A state to get.
- * @param {string} label The label of a `break` statement.
- * @returns {LoopContext|SwitchContext} A context for a `break` statement.
+ * @param {CodePathState} state The state to search within.
+ * @param {string|null} label The label of a `break` statement.
+ * @returns {BreakContext} A context for a `break` statement.
*/
function getBreakContext(state, label) {
let context = state.breakContext;
@@ -84,8 +693,10 @@ function getBreakContext(state, label) {
}
/**
- * Gets a context for a `return` statement.
- * @param {CodePathState} state A state to get.
+ * Gets a context for a `return` statement. There is just one special case:
+ * if there is a `try` statement with a `finally` block, because that alters
+ * how `return` behaves; otherwise, this just passes through the given state.
+ * @param {CodePathState} state The state to search within
* @returns {TryContext|CodePathState} A context for a `return` statement.
*/
function getReturnContext(state) {
@@ -102,8 +713,11 @@ function getReturnContext(state) {
}
/**
- * Gets a context for a `throw` statement.
- * @param {CodePathState} state A state to get.
+ * Gets a context for a `throw` statement. There is just one special case:
+ * if there is a `try` statement with a `finally` block and we are inside of
+ * a `catch` because that changes how `throw` behaves; otherwise, this just
+ * passes through the given state.
+ * @param {CodePathState} state The state to search within.
* @returns {TryContext|CodePathState} A context for a `throw` statement.
*/
function getThrowContext(state) {
@@ -122,13 +736,13 @@ function getThrowContext(state) {
}
/**
- * Removes a given element from a given array.
- * @param {any[]} xs An array to remove the specific element.
- * @param {any} x An element to be removed.
+ * Removes a given value from a given array.
+ * @param {any[]} elements An array to remove the specific element.
+ * @param {any} value The value to be removed.
* @returns {void}
*/
-function remove(xs, x) {
- xs.splice(xs.indexOf(x), 1);
+function removeFromArray(elements, value) {
+ elements.splice(elements.indexOf(value), 1);
}
/**
@@ -141,48 +755,77 @@ function remove(xs, x) {
* @param {CodePathSegment[]} nextSegments Backward segments to disconnect.
* @returns {void}
*/
-function removeConnection(prevSegments, nextSegments) {
+function disconnectSegments(prevSegments, nextSegments) {
for (let i = 0; i < prevSegments.length; ++i) {
const prevSegment = prevSegments[i];
const nextSegment = nextSegments[i];
- remove(prevSegment.nextSegments, nextSegment);
- remove(prevSegment.allNextSegments, nextSegment);
- remove(nextSegment.prevSegments, prevSegment);
- remove(nextSegment.allPrevSegments, prevSegment);
+ removeFromArray(prevSegment.nextSegments, nextSegment);
+ removeFromArray(prevSegment.allNextSegments, nextSegment);
+ removeFromArray(nextSegment.prevSegments, prevSegment);
+ removeFromArray(nextSegment.allPrevSegments, prevSegment);
}
}
/**
- * Creates looping path.
- * @param {CodePathState} state The instance.
+ * Creates looping path between two arrays of segments, ensuring that there are
+ * paths going between matching segments in the arrays.
+ * @param {CodePathState} state The state to operate on.
* @param {CodePathSegment[]} unflattenedFromSegments Segments which are source.
* @param {CodePathSegment[]} unflattenedToSegments Segments which are destination.
* @returns {void}
*/
function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
+
const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments);
const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments);
-
const end = Math.min(fromSegments.length, toSegments.length);
+ /*
+ * This loop effectively updates a doubly-linked list between two collections
+ * of segments making sure that segments in the same array indices are
+ * combined to create a path.
+ */
for (let i = 0; i < end; ++i) {
+
+ // get the segments in matching array indices
const fromSegment = fromSegments[i];
const toSegment = toSegments[i];
+ /*
+ * If the destination segment is reachable, then create a path from the
+ * source segment to the destination segment.
+ */
if (toSegment.reachable) {
fromSegment.nextSegments.push(toSegment);
}
+
+ /*
+ * If the source segment is reachable, then create a path from the
+ * destination segment back to the source segment.
+ */
if (fromSegment.reachable) {
toSegment.prevSegments.push(fromSegment);
}
+
+ /*
+ * Also update the arrays that don't care if the segments are reachable
+ * or not. This should always happen regardless of anything else.
+ */
fromSegment.allNextSegments.push(toSegment);
toSegment.allPrevSegments.push(fromSegment);
+ /*
+ * If the destination segment has at least two previous segments in its
+ * path then that means there was one previous segment before this iteration
+ * of the loop was executed. So, we need to mark the source segment as
+ * looped.
+ */
if (toSegment.allPrevSegments.length >= 2) {
CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
}
+ // let the code path analyzer know that there's been a loop created
state.notifyLooped(fromSegment, toSegment);
}
}
@@ -198,15 +841,27 @@ function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
* @returns {void}
*/
function finalizeTestSegmentsOfFor(context, choiceContext, head) {
+
+ /*
+ * If this choice context doesn't already contain paths from a
+ * child context, then add the current head to each potential path.
+ */
if (!choiceContext.processed) {
choiceContext.trueForkContext.add(head);
choiceContext.falseForkContext.add(head);
- choiceContext.qqForkContext.add(head);
+ choiceContext.nullishForkContext.add(head);
}
+ /*
+ * If the test condition isn't a hardcoded truthy value, then `break`
+ * must follow the same path as if the test condition is false. To represent
+ * that, we append the path for when the loop test is false (represented by
+ * `falseForkContext`) to the `brokenForkContext`.
+ */
if (context.test !== true) {
context.brokenForkContext.addAll(choiceContext.falseForkContext);
}
+
context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1);
}
@@ -220,35 +875,124 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
class CodePathState {
/**
+ * Creates a new instance.
* @param {IdGenerator} idGenerator An id generator to generate id for code
* path segments.
* @param {Function} onLooped A callback function to notify looping.
*/
constructor(idGenerator, onLooped) {
+
+ /**
+ * The ID generator to use when creating new segments.
+ * @type {IdGenerator}
+ */
this.idGenerator = idGenerator;
+
+ /**
+ * A callback function to call when there is a loop.
+ * @type {Function}
+ */
this.notifyLooped = onLooped;
+
+ /**
+ * The root fork context for this state.
+ * @type {ForkContext}
+ */
this.forkContext = ForkContext.newRoot(idGenerator);
+
+ /**
+ * Context for logical expressions, conditional expressions, `if` statements,
+ * and loops.
+ * @type {ChoiceContext}
+ */
this.choiceContext = null;
+
+ /**
+ * Context for `switch` statements.
+ * @type {SwitchContext}
+ */
this.switchContext = null;
+
+ /**
+ * Context for `try` statements.
+ * @type {TryContext}
+ */
this.tryContext = null;
+
+ /**
+ * Context for loop statements.
+ * @type {LoopContext}
+ */
this.loopContext = null;
+
+ /**
+ * Context for `break` statements.
+ * @type {BreakContext}
+ */
this.breakContext = null;
+
+ /**
+ * Context for `ChainExpression` nodes.
+ * @type {ChainContext}
+ */
this.chainContext = null;
+ /**
+ * An array that tracks the current segments in the state. The array
+ * starts empty and segments are added with each `onCodePathSegmentStart`
+ * event and removed with each `onCodePathSegmentEnd` event. Effectively,
+ * this is tracking the code path segment traversal as the state is
+ * modified.
+ * @type {Array}
+ */
this.currentSegments = [];
+
+ /**
+ * Tracks the starting segment for this path. This value never changes.
+ * @type {CodePathSegment}
+ */
this.initialSegment = this.forkContext.head[0];
- // returnedSegments and thrownSegments push elements into finalSegments also.
- const final = this.finalSegments = [];
- const returned = this.returnedForkContext = [];
- const thrown = this.thrownForkContext = [];
+ /**
+ * The final segments of the code path which are either `return` or `throw`.
+ * This is a union of the segments in `returnedForkContext` and `thrownForkContext`.
+ * @type {Array}
+ */
+ this.finalSegments = [];
+
+ /**
+ * The final segments of the code path which are `return`. These
+ * segments are also contained in `finalSegments`.
+ * @type {Array}
+ */
+ this.returnedForkContext = [];
+
+ /**
+ * The final segments of the code path which are `throw`. These
+ * segments are also contained in `finalSegments`.
+ * @type {Array}
+ */
+ this.thrownForkContext = [];
+
+ /*
+ * We add an `add` method so that these look more like fork contexts and
+ * can be used interchangeably when a fork context is needed to add more
+ * segments to a path.
+ *
+ * Ultimately, we want anything added to `returned` or `thrown` to also
+ * be added to `final`. We only add reachable and used segments to these
+ * arrays.
+ */
+ const final = this.finalSegments;
+ const returned = this.returnedForkContext;
+ const thrown = this.thrownForkContext;
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
}
/**
- * The head segments.
+ * A passthrough property exposing the current pointer as part of the API.
* @type {CodePathSegment[]}
*/
get headSegments() {
@@ -341,77 +1085,72 @@ class CodePathState {
* If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
* If it's IfStatement's or ConditionalExpression's, this is `"test"`.
* Otherwise, this is `"loop"`.
- * @param {boolean} isForkingAsResult A flag that shows that goes different
- * paths between `true` and `false`.
+ * @param {boolean} isForkingAsResult Indicates if the result of the choice
+ * creates a fork.
* @returns {void}
*/
pushChoiceContext(kind, isForkingAsResult) {
- this.choiceContext = {
- upper: this.choiceContext,
- kind,
- isForkingAsResult,
- trueForkContext: ForkContext.newEmpty(this.forkContext),
- falseForkContext: ForkContext.newEmpty(this.forkContext),
- qqForkContext: ForkContext.newEmpty(this.forkContext),
- processed: false
- };
+ this.choiceContext = new ChoiceContext(this.choiceContext, kind, isForkingAsResult, this.forkContext);
}
/**
* Pops the last choice context and finalizes it.
+ * This is called upon leaving a node that represents a choice.
* @throws {Error} (Unreachable.)
* @returns {ChoiceContext} The popped context.
*/
popChoiceContext() {
- const context = this.choiceContext;
-
- this.choiceContext = context.upper;
-
+ const poppedChoiceContext = this.choiceContext;
const forkContext = this.forkContext;
- const headSegments = forkContext.head;
+ const head = forkContext.head;
+
+ this.choiceContext = poppedChoiceContext.upper;
- switch (context.kind) {
+ switch (poppedChoiceContext.kind) {
case "&&":
case "||":
case "??":
/*
- * If any result were not transferred from child contexts,
- * this sets the head segments to both cases.
- * The head segments are the path of the right-hand operand.
+ * The `head` are the path of the right-hand operand.
+ * If we haven't previously added segments from child contexts,
+ * then we add these segments to all possible forks.
*/
- if (!context.processed) {
- context.trueForkContext.add(headSegments);
- context.falseForkContext.add(headSegments);
- context.qqForkContext.add(headSegments);
+ if (!poppedChoiceContext.processed) {
+ poppedChoiceContext.trueForkContext.add(head);
+ poppedChoiceContext.falseForkContext.add(head);
+ poppedChoiceContext.nullishForkContext.add(head);
}
/*
- * Transfers results to upper context if this context is in
- * test chunk.
+ * If this context is the left (test) expression for another choice
+ * context, such as `a || b` in the expression `a || b || c`,
+ * then we take the segments for this context and move them up
+ * to the parent context.
*/
- if (context.isForkingAsResult) {
+ if (poppedChoiceContext.isForkingAsResult) {
const parentContext = this.choiceContext;
- parentContext.trueForkContext.addAll(context.trueForkContext);
- parentContext.falseForkContext.addAll(context.falseForkContext);
- parentContext.qqForkContext.addAll(context.qqForkContext);
+ parentContext.trueForkContext.addAll(poppedChoiceContext.trueForkContext);
+ parentContext.falseForkContext.addAll(poppedChoiceContext.falseForkContext);
+ parentContext.nullishForkContext.addAll(poppedChoiceContext.nullishForkContext);
parentContext.processed = true;
- return context;
+ // Exit early so we don't collapse all paths into one.
+ return poppedChoiceContext;
}
break;
case "test":
- if (!context.processed) {
+ if (!poppedChoiceContext.processed) {
/*
* The head segments are the path of the `if` block here.
* Updates the `true` path with the end of the `if` block.
*/
- context.trueForkContext.clear();
- context.trueForkContext.add(headSegments);
+ poppedChoiceContext.trueForkContext.clear();
+ poppedChoiceContext.trueForkContext.add(head);
} else {
/*
@@ -419,8 +1158,8 @@ class CodePathState {
* Updates the `false` path with the end of the `else`
* block.
*/
- context.falseForkContext.clear();
- context.falseForkContext.add(headSegments);
+ poppedChoiceContext.falseForkContext.clear();
+ poppedChoiceContext.falseForkContext.add(head);
}
break;
@@ -428,82 +1167,129 @@ class CodePathState {
case "loop":
/*
- * Loops are addressed in popLoopContext().
- * This is called from popLoopContext().
+ * Loops are addressed in `popLoopContext()` so just return
+ * the context without modification.
*/
- return context;
+ return poppedChoiceContext;
/* c8 ignore next */
default:
throw new Error("unreachable");
}
- // Merges all paths.
- const prevForkContext = context.trueForkContext;
+ /*
+ * Merge the true path with the false path to create a single path.
+ */
+ const combinedForkContext = poppedChoiceContext.trueForkContext;
- prevForkContext.addAll(context.falseForkContext);
- forkContext.replaceHead(prevForkContext.makeNext(0, -1));
+ combinedForkContext.addAll(poppedChoiceContext.falseForkContext);
+ forkContext.replaceHead(combinedForkContext.makeNext(0, -1));
- return context;
+ return poppedChoiceContext;
}
/**
- * Makes a code path segment of the right-hand operand of a logical
+ * Creates a code path segment to represent right-hand operand of a logical
* expression.
+ * This is called in the preprocessing phase when entering a node.
* @throws {Error} (Unreachable.)
* @returns {void}
*/
makeLogicalRight() {
- const context = this.choiceContext;
+ const currentChoiceContext = this.choiceContext;
const forkContext = this.forkContext;
- if (context.processed) {
+ if (currentChoiceContext.processed) {
/*
- * This got segments already from the child choice context.
- * Creates the next path from own true/false fork context.
+ * This context was already assigned segments from a child
+ * choice context. In this case, we are concerned only about
+ * the path that does not short-circuit and so ends up on the
+ * right-hand operand of the logical expression.
*/
let prevForkContext;
- switch (context.kind) {
+ switch (currentChoiceContext.kind) {
case "&&": // if true then go to the right-hand side.
- prevForkContext = context.trueForkContext;
+ prevForkContext = currentChoiceContext.trueForkContext;
break;
case "||": // if false then go to the right-hand side.
- prevForkContext = context.falseForkContext;
+ prevForkContext = currentChoiceContext.falseForkContext;
break;
- case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's qqForkContext.
- prevForkContext = context.qqForkContext;
+ case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext.
+ prevForkContext = currentChoiceContext.nullishForkContext;
break;
default:
throw new Error("unreachable");
}
+ /*
+ * Create the segment for the right-hand operand of the logical expression
+ * and adjust the fork context pointer to point there. The right-hand segment
+ * is added at the end of all segments in `prevForkContext`.
+ */
forkContext.replaceHead(prevForkContext.makeNext(0, -1));
+
+ /*
+ * We no longer need this list of segments.
+ *
+ * Reset `processed` because we've removed the segments from the child
+ * choice context. This allows `popChoiceContext()` to continue adding
+ * segments later.
+ */
prevForkContext.clear();
- context.processed = false;
+ currentChoiceContext.processed = false;
+
} else {
/*
- * This did not get segments from the child choice context.
- * So addresses the head segments.
- * The head segments are the path of the left-hand operand.
+ * This choice context was not assigned segments from a child
+ * choice context, which means that it's a terminal logical
+ * expression.
+ *
+ * `head` is the segments for the left-hand operand of the
+ * logical expression.
+ *
+ * Each of the fork contexts below are empty at this point. We choose
+ * the path(s) that will short-circuit and add the segment for the
+ * left-hand operand to it. Ultimately, this will be the only segment
+ * in that path due to the short-circuting, so we are just seeding
+ * these paths to start.
*/
- switch (context.kind) {
- case "&&": // the false path can short-circuit.
- context.falseForkContext.add(forkContext.head);
+ switch (currentChoiceContext.kind) {
+ case "&&":
+
+ /*
+ * In most contexts, when a && expression evaluates to false,
+ * it short circuits, so we need to account for that by setting
+ * the `falseForkContext` to the left operand.
+ *
+ * When a && expression is the left-hand operand for a ??
+ * expression, such as `(a && b) ?? c`, a nullish value will
+ * also short-circuit in a different way than a false value,
+ * so we also set the `nullishForkContext` to the left operand.
+ * This path is only used with a ?? expression and is thrown
+ * away for any other type of logical expression, so it's safe
+ * to always add.
+ */
+ currentChoiceContext.falseForkContext.add(forkContext.head);
+ currentChoiceContext.nullishForkContext.add(forkContext.head);
break;
case "||": // the true path can short-circuit.
- context.trueForkContext.add(forkContext.head);
+ currentChoiceContext.trueForkContext.add(forkContext.head);
break;
case "??": // both can short-circuit.
- context.trueForkContext.add(forkContext.head);
- context.falseForkContext.add(forkContext.head);
+ currentChoiceContext.trueForkContext.add(forkContext.head);
+ currentChoiceContext.falseForkContext.add(forkContext.head);
break;
default:
throw new Error("unreachable");
}
+ /*
+ * Create the segment for the right-hand operand of the logical expression
+ * and adjust the fork context pointer to point there.
+ */
forkContext.replaceHead(forkContext.makeNext(-1, -1));
}
}
@@ -524,7 +1310,7 @@ class CodePathState {
if (!context.processed) {
context.trueForkContext.add(forkContext.head);
context.falseForkContext.add(forkContext.head);
- context.qqForkContext.add(forkContext.head);
+ context.nullishForkContext.add(forkContext.head);
}
context.processed = false;
@@ -562,22 +1348,20 @@ class CodePathState {
//--------------------------------------------------------------------------
/**
- * Push a new `ChainExpression` context to the stack.
- * This method is called on entering to each `ChainExpression` node.
- * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
+ * Pushes a new `ChainExpression` context to the stack. This method is
+ * called when entering a `ChainExpression` node. A chain context is used to
+ * count forking in the optional chain then merge them on the exiting from the
+ * `ChainExpression` node.
* @returns {void}
*/
pushChainContext() {
- this.chainContext = {
- upper: this.chainContext,
- countChoiceContexts: 0
- };
+ this.chainContext = new ChainContext(this.chainContext);
}
/**
- * Pop a `ChainExpression` context from the stack.
- * This method is called on exiting from each `ChainExpression` node.
- * This merges all forks of the last optional chaining.
+ * Pop a `ChainExpression` context from the stack. This method is called on
+ * exiting from each `ChainExpression` node. This merges all forks of the
+ * last optional chaining.
* @returns {void}
*/
popChainContext() {
@@ -586,7 +1370,7 @@ class CodePathState {
this.chainContext = context.upper;
// pop all choice contexts of this.
- for (let i = context.countChoiceContexts; i > 0; --i) {
+ for (let i = context.choiceContextCount; i > 0; --i) {
this.popChoiceContext();
}
}
@@ -599,7 +1383,7 @@ class CodePathState {
*/
makeOptionalNode() {
if (this.chainContext) {
- this.chainContext.countChoiceContexts += 1;
+ this.chainContext.choiceContextCount += 1;
this.pushChoiceContext("??", false);
}
}
@@ -627,16 +1411,7 @@ class CodePathState {
* @returns {void}
*/
pushSwitchContext(hasCase, label) {
- this.switchContext = {
- upper: this.switchContext,
- hasCase,
- defaultSegments: null,
- defaultBodySegments: null,
- foundDefault: false,
- lastIsDefault: false,
- countForks: 0
- };
-
+ this.switchContext = new SwitchContext(this.switchContext, hasCase);
this.pushBreakContext(true, label);
}
@@ -657,7 +1432,7 @@ class CodePathState {
const forkContext = this.forkContext;
const brokenForkContext = this.popBreakContext().brokenForkContext;
- if (context.countForks === 0) {
+ if (context.forkCount === 0) {
/*
* When there is only one `default` chunk and there is one or more
@@ -684,47 +1459,54 @@ class CodePathState {
brokenForkContext.add(lastSegments);
/*
- * A path which is failed in all case test should be connected to path
- * of `default` chunk.
+ * Any value that doesn't match a `case` test should flow to the default
+ * case. That happens normally when the default case is last in the `switch`,
+ * but if it's not, we need to rewire some of the paths to be correct.
*/
if (!context.lastIsDefault) {
if (context.defaultBodySegments) {
/*
- * Remove a link from `default` label to its chunk.
- * It's false route.
+ * There is a non-empty default case, so remove the path from the `default`
+ * label to its body for an accurate representation.
+ */
+ disconnectSegments(context.defaultSegments, context.defaultBodySegments);
+
+ /*
+ * Connect the path from the last non-default case to the body of the
+ * default case.
*/
- removeConnection(context.defaultSegments, context.defaultBodySegments);
makeLooped(this, lastCaseSegments, context.defaultBodySegments);
+
} else {
/*
- * It handles the last case body as broken if `default` chunk
- * does not exist.
+ * There is no default case, so we treat this as if the last case
+ * had a `break` in it.
*/
brokenForkContext.add(lastCaseSegments);
}
}
- // Pops the segment context stack until the entry segment.
- for (let i = 0; i < context.countForks; ++i) {
+ // Traverse up to the original fork context for the `switch` statement
+ for (let i = 0; i < context.forkCount; ++i) {
this.forkContext = this.forkContext.upper;
}
/*
- * Creates a path from all brokenForkContext paths.
- * This is a path after switch statement.
+ * Creates a path from all `brokenForkContext` paths.
+ * This is a path after `switch` statement.
*/
this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
}
/**
* Makes a code path segment for a `SwitchCase` node.
- * @param {boolean} isEmpty `true` if the body is empty.
- * @param {boolean} isDefault `true` if the body is the default case.
+ * @param {boolean} isCaseBodyEmpty `true` if the body is empty.
+ * @param {boolean} isDefaultCase `true` if the body is the default case.
* @returns {void}
*/
- makeSwitchCaseBody(isEmpty, isDefault) {
+ makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) {
const context = this.switchContext;
if (!context.hasCase) {
@@ -734,7 +1516,7 @@ class CodePathState {
/*
* Merge forks.
* The parent fork context has two segments.
- * Those are from the current case and the body of the previous case.
+ * Those are from the current `case` and the body of the previous case.
*/
const parentForkContext = this.forkContext;
const forkContext = this.pushForkContext();
@@ -742,26 +1524,53 @@ class CodePathState {
forkContext.add(parentForkContext.makeNext(0, -1));
/*
- * Save `default` chunk info.
- * If the `default` label is not at the last, we must make a path from
- * the last `case` to the `default` chunk.
+ * Add information about the default case.
+ *
+ * The purpose of this is to identify the starting segments for the
+ * default case to make sure there is a path there.
*/
- if (isDefault) {
+ if (isDefaultCase) {
+
+ /*
+ * This is the default case in the `switch`.
+ *
+ * We first save the current pointer as `defaultSegments` to point
+ * to the `default` keyword.
+ */
context.defaultSegments = parentForkContext.head;
- if (isEmpty) {
- context.foundDefault = true;
+
+ /*
+ * If the body of the case is empty then we just set
+ * `foundEmptyDefault` to true; otherwise, we save a reference
+ * to the current pointer as `defaultBodySegments`.
+ */
+ if (isCaseBodyEmpty) {
+ context.foundEmptyDefault = true;
} else {
context.defaultBodySegments = forkContext.head;
}
+
} else {
- if (!isEmpty && context.foundDefault) {
- context.foundDefault = false;
+
+ /*
+ * This is not the default case in the `switch`.
+ *
+ * If it's not empty and there is already an empty default case found,
+ * that means the default case actually comes before this case,
+ * and that it will fall through to this case. So, we can now
+ * ignore the previous default case (reset `foundEmptyDefault` to false)
+ * and set `defaultBodySegments` to the current segments because this is
+ * effectively the new default case.
+ */
+ if (!isCaseBodyEmpty && context.foundEmptyDefault) {
+ context.foundEmptyDefault = false;
context.defaultBodySegments = forkContext.head;
}
}
- context.lastIsDefault = isDefault;
- context.countForks += 1;
+ // keep track if the default case ends up last
+ context.lastIsDefault = isDefaultCase;
+ context.forkCount += 1;
}
//--------------------------------------------------------------------------
@@ -775,19 +1584,7 @@ class CodePathState {
* @returns {void}
*/
pushTryContext(hasFinalizer) {
- this.tryContext = {
- upper: this.tryContext,
- position: "try",
- hasFinalizer,
-
- returnedForkContext: hasFinalizer
- ? ForkContext.newEmpty(this.forkContext)
- : null,
-
- thrownForkContext: ForkContext.newEmpty(this.forkContext),
- lastOfTryIsReachable: false,
- lastOfCatchIsReachable: false
- };
+ this.tryContext = new TryContext(this.tryContext, hasFinalizer, this.forkContext);
}
/**
@@ -799,25 +1596,35 @@ class CodePathState {
this.tryContext = context.upper;
+ /*
+ * If we're inside the `catch` block, that means there is no `finally`,
+ * so we can process the `try` and `catch` blocks the simple way and
+ * merge their two paths.
+ */
if (context.position === "catch") {
-
- // Merges two paths from the `try` block and `catch` block merely.
this.popForkContext();
return;
}
/*
- * The following process is executed only when there is the `finally`
+ * The following process is executed only when there is a `finally`
* block.
*/
- const returned = context.returnedForkContext;
- const thrown = context.thrownForkContext;
+ const originalReturnedForkContext = context.returnedForkContext;
+ const originalThrownForkContext = context.thrownForkContext;
- if (returned.empty && thrown.empty) {
+ // no `return` or `throw` in `try` or `catch` so there's nothing left to do
+ if (originalReturnedForkContext.empty && originalThrownForkContext.empty) {
return;
}
+ /*
+ * The following process is executed only when there is a `finally`
+ * block and there was a `return` or `throw` in the `try` or `catch`
+ * blocks.
+ */
+
// Separate head to normal paths and leaving paths.
const headSegments = this.forkContext.head;
@@ -826,10 +1633,10 @@ class CodePathState {
const leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
// Forwards the leaving path to upper contexts.
- if (!returned.empty) {
+ if (!originalReturnedForkContext.empty) {
getReturnContext(this).returnedForkContext.add(leavingSegments);
}
- if (!thrown.empty) {
+ if (!originalThrownForkContext.empty) {
getThrowContext(this).thrownForkContext.add(leavingSegments);
}
@@ -852,16 +1659,20 @@ class CodePathState {
makeCatchBlock() {
const context = this.tryContext;
const forkContext = this.forkContext;
- const thrown = context.thrownForkContext;
+ const originalThrownForkContext = context.thrownForkContext;
- // Update state.
+ /*
+ * We are now in a catch block so we need to update the context
+ * with that information. This includes creating a new fork
+ * context in case we encounter any `throw` statements here.
+ */
context.position = "catch";
context.thrownForkContext = ForkContext.newEmpty(forkContext);
context.lastOfTryIsReachable = forkContext.reachable;
- // Merge thrown paths.
- thrown.add(forkContext.head);
- const thrownSegments = thrown.makeNext(0, -1);
+ // Merge the thrown paths from the `try` and `catch` blocks
+ originalThrownForkContext.add(forkContext.head);
+ const thrownSegments = originalThrownForkContext.makeNext(0, -1);
// Fork to a bypass and the merged thrown path.
this.pushForkContext();
@@ -880,8 +1691,8 @@ class CodePathState {
makeFinallyBlock() {
const context = this.tryContext;
let forkContext = this.forkContext;
- const returned = context.returnedForkContext;
- const thrown = context.thrownForkContext;
+ const originalReturnedForkContext = context.returnedForkContext;
+ const originalThrownForContext = context.thrownForkContext;
const headOfLeavingSegments = forkContext.head;
// Update state.
@@ -895,9 +1706,15 @@ class CodePathState {
} else {
context.lastOfTryIsReachable = forkContext.reachable;
}
+
+
context.position = "finally";
- if (returned.empty && thrown.empty) {
+ /*
+ * If there was no `return` or `throw` in either the `try` or `catch`
+ * blocks, then there's no further code paths to create for `finally`.
+ */
+ if (originalReturnedForkContext.empty && originalThrownForContext.empty) {
// This path does not leave.
return;
@@ -905,18 +1722,18 @@ class CodePathState {
/*
* Create a parallel segment from merging returned and thrown.
- * This segment will leave at the end of this finally block.
+ * This segment will leave at the end of this `finally` block.
*/
const segments = forkContext.makeNext(-1, -1);
for (let i = 0; i < forkContext.count; ++i) {
const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
- for (let j = 0; j < returned.segmentsList.length; ++j) {
- prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]);
+ for (let j = 0; j < originalReturnedForkContext.segmentsList.length; ++j) {
+ prevSegsOfLeavingSegment.push(originalReturnedForkContext.segmentsList[j][i]);
}
- for (let j = 0; j < thrown.segmentsList.length; ++j) {
- prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]);
+ for (let j = 0; j < originalThrownForContext.segmentsList.length; ++j) {
+ prevSegsOfLeavingSegment.push(originalThrownForContext.segmentsList[j][i]);
}
segments.push(
@@ -971,63 +1788,32 @@ class CodePathState {
*/
pushLoopContext(type, label) {
const forkContext = this.forkContext;
+
+ // All loops need a path to account for `break` statements
const breakContext = this.pushBreakContext(true, label);
switch (type) {
case "WhileStatement":
this.pushChoiceContext("loop", false);
- this.loopContext = {
- upper: this.loopContext,
- type,
- label,
- test: void 0,
- continueDestSegments: null,
- brokenForkContext: breakContext.brokenForkContext
- };
+ this.loopContext = new WhileLoopContext(this.loopContext, label, breakContext);
break;
case "DoWhileStatement":
this.pushChoiceContext("loop", false);
- this.loopContext = {
- upper: this.loopContext,
- type,
- label,
- test: void 0,
- entrySegments: null,
- continueForkContext: ForkContext.newEmpty(forkContext),
- brokenForkContext: breakContext.brokenForkContext
- };
+ this.loopContext = new DoWhileLoopContext(this.loopContext, label, breakContext, forkContext);
break;
case "ForStatement":
this.pushChoiceContext("loop", false);
- this.loopContext = {
- upper: this.loopContext,
- type,
- label,
- test: void 0,
- endOfInitSegments: null,
- testSegments: null,
- endOfTestSegments: null,
- updateSegments: null,
- endOfUpdateSegments: null,
- continueDestSegments: null,
- brokenForkContext: breakContext.brokenForkContext
- };
+ this.loopContext = new ForLoopContext(this.loopContext, label, breakContext);
break;
case "ForInStatement":
+ this.loopContext = new ForInLoopContext(this.loopContext, label, breakContext);
+ break;
+
case "ForOfStatement":
- this.loopContext = {
- upper: this.loopContext,
- type,
- label,
- prevSegments: null,
- leftSegments: null,
- endOfLeftSegments: null,
- continueDestSegments: null,
- brokenForkContext: breakContext.brokenForkContext
- };
+ this.loopContext = new ForOfLoopContext(this.loopContext, label, breakContext);
break;
/* c8 ignore next */
@@ -1054,6 +1840,11 @@ class CodePathState {
case "WhileStatement":
case "ForStatement":
this.popChoiceContext();
+
+ /*
+ * Creates the path from the end of the loop body up to the
+ * location where `continue` would jump to.
+ */
makeLooped(
this,
forkContext.head,
@@ -1068,11 +1859,21 @@ class CodePathState {
choiceContext.trueForkContext.add(forkContext.head);
choiceContext.falseForkContext.add(forkContext.head);
}
+
+ /*
+ * If this isn't a hardcoded `true` condition, then `break`
+ * should continue down the path as if the condition evaluated
+ * to false.
+ */
if (context.test !== true) {
brokenForkContext.addAll(choiceContext.falseForkContext);
}
- // `true` paths go to looping.
+ /*
+ * When the condition is true, the loop continues back to the top,
+ * so create a path from each possible true condition back to the
+ * top of the loop.
+ */
const segmentsList = choiceContext.trueForkContext.segmentsList;
for (let i = 0; i < segmentsList.length; ++i) {
@@ -1088,6 +1889,11 @@ class CodePathState {
case "ForInStatement":
case "ForOfStatement":
brokenForkContext.add(forkContext.head);
+
+ /*
+ * Creates the path from the end of the loop body up to the
+ * left expression (left of `in` or `of`) of the loop.
+ */
makeLooped(
this,
forkContext.head,
@@ -1100,7 +1906,14 @@ class CodePathState {
throw new Error("unreachable");
}
- // Go next.
+ /*
+ * If there wasn't a `break` statement in the loop, then we're at
+ * the end of the loop's path, so we make an unreachable segment
+ * to mark that.
+ *
+ * If there was a `break` statement, then we continue on into the
+ * `brokenForkContext`.
+ */
if (brokenForkContext.empty) {
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
} else {
@@ -1138,7 +1951,11 @@ class CodePathState {
choiceContext.falseForkContext.add(forkContext.head);
}
- // Update state.
+ /*
+ * If this isn't a hardcoded `true` condition, then `break`
+ * should continue down the path as if the condition evaluated
+ * to false.
+ */
if (context.test !== true) {
context.brokenForkContext.addAll(choiceContext.falseForkContext);
}
@@ -1170,7 +1987,11 @@ class CodePathState {
context.test = test;
- // Creates paths of `continue` statements.
+ /*
+ * If there is a `continue` statement in the loop then `continueForkContext`
+ * won't be empty. We wire up the path from `continue` to the loop
+ * test condition and then continue the traversal in the root fork context.
+ */
if (!context.continueForkContext.empty) {
context.continueForkContext.add(forkContext.head);
const testSegments = context.continueForkContext.makeNext(0, -1);
@@ -1190,7 +2011,14 @@ class CodePathState {
const endOfInitSegments = forkContext.head;
const testSegments = forkContext.makeNext(-1, -1);
- // Update state.
+ /*
+ * Update the state.
+ *
+ * The `continueDestSegments` are set to `testSegments` because we
+ * don't yet know if there is an update expression in this loop. So,
+ * from what we already know at this point, a `continue` statement
+ * will jump back to the test expression.
+ */
context.test = test;
context.endOfInitSegments = endOfInitSegments;
context.continueDestSegments = context.testSegments = testSegments;
@@ -1217,7 +2045,14 @@ class CodePathState {
context.endOfInitSegments = forkContext.head;
}
- // Update state.
+ /*
+ * Update the state.
+ *
+ * The `continueDestSegments` are now set to `updateSegments` because we
+ * know there is an update expression in this loop. So, a `continue` statement
+ * in the loop will jump to the update expression first, and then to any
+ * test expression the loop might have.
+ */
const updateSegments = forkContext.makeDisconnected(-1, -1);
context.continueDestSegments = context.updateSegments = updateSegments;
@@ -1233,11 +2068,30 @@ class CodePathState {
const choiceContext = this.choiceContext;
const forkContext = this.forkContext;
- // Update state.
+ /*
+ * Determine what to do based on which part of the `for` loop are present.
+ * 1. If there is an update expression, then `updateSegments` is not null and
+ * we need to assign `endOfUpdateSegments`, and if there is a test
+ * expression, we then need to create the looped path to get back to
+ * the test condition.
+ * 2. If there is no update expression but there is a test expression,
+ * then we only need to update the test segment information.
+ * 3. If there is no update expression and no test expression, then we
+ * just save `endOfInitSegments`.
+ */
if (context.updateSegments) {
context.endOfUpdateSegments = forkContext.head;
- // `update` -> `test`
+ /*
+ * In a `for` loop that has both an update expression and a test
+ * condition, execution flows from the test expression into the
+ * loop body, to the update expression, and then back to the test
+ * expression to determine if the loop should continue.
+ *
+ * To account for that, we need to make a path from the end of the
+ * update expression to the start of the test expression. This is
+ * effectively what creates the loop in the code path.
+ */
if (context.testSegments) {
makeLooped(
this,
@@ -1257,12 +2111,18 @@ class CodePathState {
let bodySegments = context.endOfTestSegments;
+ /*
+ * If there is a test condition, then there `endOfTestSegments` is also
+ * the start of the loop body. If there isn't a test condition then
+ * `bodySegments` will be null and we need to look elsewhere to find
+ * the start of the body.
+ *
+ * The body starts at the end of the init expression and ends at the end
+ * of the update expression, so we use those locations to determine the
+ * body segments.
+ */
if (!bodySegments) {
- /*
- * If there is not the `test` part, the `body` path comes from the
- * `init` part and the `update` part.
- */
const prevForkContext = ForkContext.newEmpty(forkContext);
prevForkContext.add(context.endOfInitSegments);
@@ -1272,7 +2132,16 @@ class CodePathState {
bodySegments = prevForkContext.makeNext(0, -1);
}
+
+ /*
+ * If there was no test condition and no update expression, then
+ * `continueDestSegments` will be null. In that case, a
+ * `continue` should skip directly to the body of the loop.
+ * Otherwise, we want to keep the current `continueDestSegments`.
+ */
context.continueDestSegments = context.continueDestSegments || bodySegments;
+
+ // move pointer to the body
forkContext.replaceHead(bodySegments);
}
@@ -1336,19 +2205,15 @@ class CodePathState {
//--------------------------------------------------------------------------
/**
- * Creates new context for BreakStatement.
- * @param {boolean} breakable The flag to indicate it can break by
- * an unlabeled BreakStatement.
- * @param {string|null} label The label of this context.
- * @returns {Object} The new context.
+ * Creates new context in which a `break` statement can be used. This occurs inside of a loop,
+ * labeled statement, or switch statement.
+ * @param {boolean} breakable Indicates if we are inside a statement where
+ * `break` without a label will exit the statement.
+ * @param {string|null} label The label associated with the statement.
+ * @returns {BreakContext} The new context.
*/
pushBreakContext(breakable, label) {
- this.breakContext = {
- upper: this.breakContext,
- breakable,
- label,
- brokenForkContext: ForkContext.newEmpty(this.forkContext)
- };
+ this.breakContext = new BreakContext(this.breakContext, breakable, label, this.forkContext);
return this.breakContext;
}
@@ -1380,7 +2245,7 @@ class CodePathState {
*
* It registers the head segment to a context of `break`.
* It makes new unreachable segment, then it set the head with the segment.
- * @param {string} label A label of the break statement.
+ * @param {string|null} label A label of the break statement.
* @returns {void}
*/
makeBreak(label) {
@@ -1406,7 +2271,7 @@ class CodePathState {
*
* It makes a looping path.
* It makes new unreachable segment, then it set the head with the segment.
- * @param {string} label A label of the continue statement.
+ * @param {string|null} label A label of the continue statement.
* @returns {void}
*/
makeContinue(label) {
@@ -1422,7 +2287,7 @@ class CodePathState {
if (context.continueDestSegments) {
makeLooped(this, forkContext.head, context.continueDestSegments);
- // If the context is a for-in/of loop, this effects a break also.
+ // If the context is a for-in/of loop, this affects a break also.
if (context.type === "ForInStatement" ||
context.type === "ForOfStatement"
) {
diff --git a/lib/linter/code-path-analysis/code-path.js b/lib/linter/code-path-analysis/code-path.js
index f6a88a00af92..3bf570d754bf 100644
--- a/lib/linter/code-path-analysis/code-path.js
+++ b/lib/linter/code-path-analysis/code-path.js
@@ -80,7 +80,9 @@ class CodePath {
}
/**
- * The initial code path segment.
+ * The initial code path segment. This is the segment that is at the head
+ * of the code path.
+ * This is a passthrough to the underlying `CodePathState`.
* @type {CodePathSegment}
*/
get initialSegment() {
@@ -88,8 +90,10 @@ class CodePath {
}
/**
- * Final code path segments.
- * This array is a mix of `returnedSegments` and `thrownSegments`.
+ * Final code path segments. These are the terminal (tail) segments in the
+ * code path, which is the combination of `returnedSegments` and `thrownSegments`.
+ * All segments in this array are reachable.
+ * This is a passthrough to the underlying `CodePathState`.
* @type {CodePathSegment[]}
*/
get finalSegments() {
@@ -97,9 +101,14 @@ class CodePath {
}
/**
- * Final code path segments which is with `return` statements.
- * This array contains the last path segment if it's reachable.
- * Since the reachable last path returns `undefined`.
+ * Final code path segments that represent normal completion of the code path.
+ * For functions, this means both explicit `return` statements and implicit returns,
+ * such as the last reachable segment in a function that does not have an
+ * explicit `return` as this implicitly returns `undefined`. For scripts,
+ * modules, class field initializers, and class static blocks, this means
+ * all lines of code have been executed.
+ * These segments are also present in `finalSegments`.
+ * This is a passthrough to the underlying `CodePathState`.
* @type {CodePathSegment[]}
*/
get returnedSegments() {
@@ -107,7 +116,9 @@ class CodePath {
}
/**
- * Final code path segments which is with `throw` statements.
+ * Final code path segments that represent `throw` statements.
+ * This is a passthrough to the underlying `CodePathState`.
+ * These segments are also present in `finalSegments`.
* @type {CodePathSegment[]}
*/
get thrownSegments() {
@@ -115,7 +126,12 @@ class CodePath {
}
/**
- * Current code path segments.
+ * Tracks the traversal of the code path through each segment. This array
+ * starts empty and segments are added or removed as the code path is
+ * traversed. This array always ends up empty at the end of a code path
+ * traversal. The `CodePathState` uses this to track its progress through
+ * the code path.
+ * This is a passthrough to the underlying `CodePathState`.
* @type {CodePathSegment[]}
* @deprecated
*/
@@ -126,46 +142,70 @@ class CodePath {
/**
* Traverses all segments in this code path.
*
- * codePath.traverseSegments(function(segment, controller) {
+ * codePath.traverseSegments((segment, controller) => {
* // do something.
* });
*
* This method enumerates segments in order from the head.
*
- * The `controller` object has two methods.
+ * The `controller` argument has two methods:
*
- * - `controller.skip()` - Skip the following segments in this branch.
- * - `controller.break()` - Skip all following segments.
- * @param {Object} [options] Omittable.
- * @param {CodePathSegment} [options.first] The first segment to traverse.
- * @param {CodePathSegment} [options.last] The last segment to traverse.
+ * - `skip()` - skips the following segments in this branch
+ * - `break()` - skips all following segments in the traversal
+ *
+ * A note on the parameters: the `options` argument is optional. This means
+ * the first argument might be an options object or the callback function.
+ * @param {Object} [optionsOrCallback] Optional first and last segments to traverse.
+ * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse.
+ * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse.
* @param {Function} callback A callback function.
* @returns {void}
*/
- traverseSegments(options, callback) {
+ traverseSegments(optionsOrCallback, callback) {
+
+ // normalize the arguments into a callback and options
let resolvedOptions;
let resolvedCallback;
- if (typeof options === "function") {
- resolvedCallback = options;
+ if (typeof optionsOrCallback === "function") {
+ resolvedCallback = optionsOrCallback;
resolvedOptions = {};
} else {
- resolvedOptions = options || {};
+ resolvedOptions = optionsOrCallback || {};
resolvedCallback = callback;
}
+ // determine where to start traversing from based on the options
const startSegment = resolvedOptions.first || this.internal.initialSegment;
const lastSegment = resolvedOptions.last;
- let item = null;
+ // set up initial location information
+ let record = null;
let index = 0;
let end = 0;
let segment = null;
- const visited = Object.create(null);
+
+ // segments that have already been visited during traversal
+ const visited = new Set();
+
+ // tracks the traversal steps
const stack = [[startSegment, 0]];
+
+ // tracks the last skipped segment during traversal
let skippedSegment = null;
+
+ // indicates if we exited early from the traversal
let broken = false;
+
+ /**
+ * Maintains traversal state.
+ */
const controller = {
+
+ /**
+ * Skip the following segments in this branch.
+ * @returns {void}
+ */
skip() {
if (stack.length <= 1) {
broken = true;
@@ -173,32 +213,52 @@ class CodePath {
skippedSegment = stack[stack.length - 2][0];
}
},
+
+ /**
+ * Stop traversal completely - do not traverse to any
+ * other segments.
+ * @returns {void}
+ */
break() {
broken = true;
}
};
/**
- * Checks a given previous segment has been visited.
+ * Checks if a given previous segment has been visited.
* @param {CodePathSegment} prevSegment A previous segment to check.
* @returns {boolean} `true` if the segment has been visited.
*/
function isVisited(prevSegment) {
return (
- visited[prevSegment.id] ||
+ visited.has(prevSegment) ||
segment.isLoopedPrevSegment(prevSegment)
);
}
+ // the traversal
while (stack.length > 0) {
- item = stack[stack.length - 1];
- segment = item[0];
- index = item[1];
+
+ /*
+ * This isn't a pure stack. We use the top record all the time
+ * but don't always pop it off. The record is popped only if
+ * one of the following is true:
+ *
+ * 1) We have already visited the segment.
+ * 2) We have not visited *all* of the previous segments.
+ * 3) We have traversed past the available next segments.
+ *
+ * Otherwise, we just read the value and sometimes modify the
+ * record as we traverse.
+ */
+ record = stack[stack.length - 1];
+ segment = record[0];
+ index = record[1];
if (index === 0) {
// Skip if this segment has been visited already.
- if (visited[segment.id]) {
+ if (visited.has(segment)) {
stack.pop();
continue;
}
@@ -212,18 +272,29 @@ class CodePath {
continue;
}
- // Reset the flag of skipping if all branches have been skipped.
+ // Reset the skipping flag if all branches have been skipped.
if (skippedSegment && segment.prevSegments.includes(skippedSegment)) {
skippedSegment = null;
}
- visited[segment.id] = true;
+ visited.add(segment);
- // Call the callback when the first time.
+ /*
+ * If the most recent segment hasn't been skipped, then we call
+ * the callback, passing in the segment and the controller.
+ */
if (!skippedSegment) {
resolvedCallback.call(this, segment, controller);
+
+ // exit if we're at the last segment
if (segment === lastSegment) {
controller.skip();
}
+
+ /*
+ * If the previous statement was executed, or if the callback
+ * called a method on the controller, we might need to exit the
+ * loop, so check for that and break accordingly.
+ */
if (broken) {
break;
}
@@ -233,12 +304,35 @@ class CodePath {
// Update the stack.
end = segment.nextSegments.length - 1;
if (index < end) {
- item[1] += 1;
+
+ /*
+ * If we haven't yet visited all of the next segments, update
+ * the current top record on the stack to the next index to visit
+ * and then push a record for the current segment on top.
+ *
+ * Setting the current top record's index lets us know how many
+ * times we've been here and ensures that the segment won't be
+ * reprocessed (because we only process segments with an index
+ * of 0).
+ */
+ record[1] += 1;
stack.push([segment.nextSegments[index], 0]);
} else if (index === end) {
- item[0] = segment.nextSegments[index];
- item[1] = 0;
+
+ /*
+ * If we are at the last next segment, then reset the top record
+ * in the stack to next segment and set its index to 0 so it will
+ * be processed next.
+ */
+ record[0] = segment.nextSegments[index];
+ record[1] = 0;
} else {
+
+ /*
+ * If index > end, that means we have no more segments that need
+ * processing. So, we pop that record off of the stack in order to
+ * continue traversing at the next level up.
+ */
stack.pop();
}
}
diff --git a/lib/linter/code-path-analysis/fork-context.js b/lib/linter/code-path-analysis/fork-context.js
index 04c59b5e4172..33140272f53b 100644
--- a/lib/linter/code-path-analysis/fork-context.js
+++ b/lib/linter/code-path-analysis/fork-context.js
@@ -21,8 +21,8 @@ const assert = require("assert"),
//------------------------------------------------------------------------------
/**
- * Gets whether or not a given segment is reachable.
- * @param {CodePathSegment} segment A segment to get.
+ * Determines whether or not a given segment is reachable.
+ * @param {CodePathSegment} segment The segment to check.
* @returns {boolean} `true` if the segment is reachable.
*/
function isReachable(segment) {
@@ -30,32 +30,64 @@ function isReachable(segment) {
}
/**
- * Creates new segments from the specific range of `context.segmentsList`.
+ * Creates a new segment for each fork in the given context and appends it
+ * to the end of the specified range of segments. Ultimately, this ends up calling
+ * `new CodePathSegment()` for each of the forks using the `create` argument
+ * as a wrapper around special behavior.
+ *
+ * The `startIndex` and `endIndex` arguments specify a range of segments in
+ * `context` that should become `allPrevSegments` for the newly created
+ * `CodePathSegment` objects.
*
* When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
- * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`.
- * This `h` is from `b`, `d`, and `f`.
- * @param {ForkContext} context An instance.
- * @param {number} begin The first index of the previous segments.
- * @param {number} end The last index of the previous segments.
- * @param {Function} create A factory function of new segments.
- * @returns {CodePathSegment[]} New segments.
+ * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to
+ * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of
+ * `b`, `d`, and `f`.
+ * @param {ForkContext} context An instance from which the previous segments
+ * will be obtained.
+ * @param {number} startIndex The index of the first segment in the context
+ * that should be specified as previous segments for the newly created segments.
+ * @param {number} endIndex The index of the last segment in the context
+ * that should be specified as previous segments for the newly created segments.
+ * @param {Function} create A function that creates new `CodePathSegment`
+ * instances in a particular way. See the `CodePathSegment.new*` methods.
+ * @returns {Array} An array of the newly-created segments.
*/
-function makeSegments(context, begin, end, create) {
+function createSegments(context, startIndex, endIndex, create) {
+
+ /** @type {Array>} */
const list = context.segmentsList;
- const normalizedBegin = begin >= 0 ? begin : list.length + begin;
- const normalizedEnd = end >= 0 ? end : list.length + end;
+ /*
+ * Both `startIndex` and `endIndex` work the same way: if the number is zero
+ * or more, then the number is used as-is. If the number is negative,
+ * then that number is added to the length of the segments list to
+ * determine the index to use. That means -1 for either argument
+ * is the last element, -2 is the second to last, and so on.
+ *
+ * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the
+ * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function
+ * will include items at indices 0, 1, and 2.
+ *
+ * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only
+ * be using the last segment in `list`.
+ */
+ const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex;
+ const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex;
+ /** @type {Array} */
const segments = [];
for (let i = 0; i < context.count; ++i) {
+
+ // this is passed into `new CodePathSegment` to add to code path.
const allPrevSegments = [];
for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
allPrevSegments.push(list[j][i]);
}
+ // note: `create` is just a wrapper that augments `new CodePathSegment`.
segments.push(create(context.idGenerator.next(), allPrevSegments));
}
@@ -63,28 +95,57 @@ function makeSegments(context, begin, end, create) {
}
/**
- * `segments` becomes doubly in a `finally` block. Then if a code path exits by a
- * control statement (such as `break`, `continue`) from the `finally` block, the
- * destination's segments may be half of the source segments. In that case, this
- * merges segments.
- * @param {ForkContext} context An instance.
- * @param {CodePathSegment[]} segments Segments to merge.
- * @returns {CodePathSegment[]} The merged segments.
+ * Inside of a `finally` block we end up with two parallel paths. If the code path
+ * exits by a control statement (such as `break` or `continue`) from the `finally`
+ * block, then we need to merge the remaining parallel paths back into one.
+ * @param {ForkContext} context The fork context to work on.
+ * @param {Array} segments Segments to merge.
+ * @returns {Array} The merged segments.
*/
function mergeExtraSegments(context, segments) {
let currentSegments = segments;
+ /*
+ * We need to ensure that the array returned from this function contains no more
+ * than the number of segments that the context allows. `context.count` indicates
+ * how many items should be in the returned array to ensure that the new segment
+ * entries will line up with the already existing segment entries.
+ */
while (currentSegments.length > context.count) {
const merged = [];
- for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {
+ /*
+ * Because `context.count` is a factor of 2 inside of a `finally` block,
+ * we can divide the segment count by 2 to merge the paths together.
+ * This loops through each segment in the list and creates a new `CodePathSegment`
+ * that has the segment and the segment two slots away as previous segments.
+ *
+ * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such
+ * that:
+ *
+ * When `i` is 0:
+ * a->e
+ * c->e
+ *
+ * When `i` is 1:
+ * b->f
+ * d->f
+ */
+ for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) {
merged.push(CodePathSegment.newNext(
context.idGenerator.next(),
[currentSegments[i], currentSegments[i + length]]
));
}
+
+ /*
+ * Go through the loop condition one more time to see if we have the
+ * number of segments for the context. If not, we'll keep merging paths
+ * of the merged segments until we get there.
+ */
currentSegments = merged;
}
+
return currentSegments;
}
@@ -93,25 +154,55 @@ function mergeExtraSegments(context, segments) {
//------------------------------------------------------------------------------
/**
- * A class to manage forking.
+ * Manages the forking of code paths.
*/
class ForkContext {
/**
+ * Creates a new instance.
* @param {IdGenerator} idGenerator An identifier generator for segments.
- * @param {ForkContext|null} upper An upper fork context.
- * @param {number} count A number of parallel segments.
+ * @param {ForkContext|null} upper The preceding fork context.
+ * @param {number} count The number of parallel segments in each element
+ * of `segmentsList`.
*/
constructor(idGenerator, upper, count) {
+
+ /**
+ * The ID generator that will generate segment IDs for any new
+ * segments that are created.
+ * @type {IdGenerator}
+ */
this.idGenerator = idGenerator;
+
+ /**
+ * The preceding fork context.
+ * @type {ForkContext|null}
+ */
this.upper = upper;
+
+ /**
+ * The number of elements in each element of `segmentsList`. In most
+ * cases, this is 1 but can be 2 when there is a `finally` present,
+ * which forks the code path outside of normal flow. In the case of nested
+ * `finally` blocks, this can be a multiple of 2.
+ * @type {number}
+ */
this.count = count;
+
+ /**
+ * The segments within this context. Each element in this array has
+ * `count` elements that represent one step in each fork. For example,
+ * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path
+ * a->c->e and one path b->d->f, and `count` is 2 because each element
+ * is an array with two elements.
+ * @type {Array>}
+ */
this.segmentsList = [];
}
/**
- * The head segments.
- * @type {CodePathSegment[]}
+ * The segments that begin this fork context.
+ * @type {Array}
*/
get head() {
const list = this.segmentsList;
@@ -120,7 +211,7 @@ class ForkContext {
}
/**
- * A flag which shows empty.
+ * Indicates if the context contains no segments.
* @type {boolean}
*/
get empty() {
@@ -128,7 +219,7 @@ class ForkContext {
}
/**
- * A flag which shows reachable.
+ * Indicates if there are any segments that are reachable.
* @type {boolean}
*/
get reachable() {
@@ -138,75 +229,82 @@ class ForkContext {
}
/**
- * Creates new segments from this context.
- * @param {number} begin The first index of previous segments.
- * @param {number} end The last index of previous segments.
- * @returns {CodePathSegment[]} New segments.
+ * Creates new segments in this context and appends them to the end of the
+ * already existing `CodePathSegment`s specified by `startIndex` and
+ * `endIndex`.
+ * @param {number} startIndex The index of the first segment in the context
+ * that should be specified as previous segments for the newly created segments.
+ * @param {number} endIndex The index of the last segment in the context
+ * that should be specified as previous segments for the newly created segments.
+ * @returns {Array} An array of the newly created segments.
*/
- makeNext(begin, end) {
- return makeSegments(this, begin, end, CodePathSegment.newNext);
+ makeNext(startIndex, endIndex) {
+ return createSegments(this, startIndex, endIndex, CodePathSegment.newNext);
}
/**
- * Creates new segments from this context.
- * The new segments is always unreachable.
- * @param {number} begin The first index of previous segments.
- * @param {number} end The last index of previous segments.
- * @returns {CodePathSegment[]} New segments.
+ * Creates new unreachable segments in this context and appends them to the end of the
+ * already existing `CodePathSegment`s specified by `startIndex` and
+ * `endIndex`.
+ * @param {number} startIndex The index of the first segment in the context
+ * that should be specified as previous segments for the newly created segments.
+ * @param {number} endIndex The index of the last segment in the context
+ * that should be specified as previous segments for the newly created segments.
+ * @returns {Array} An array of the newly created segments.
*/
- makeUnreachable(begin, end) {
- return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
+ makeUnreachable(startIndex, endIndex) {
+ return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable);
}
/**
- * Creates new segments from this context.
- * The new segments don't have connections for previous segments.
- * But these inherit the reachable flag from this context.
- * @param {number} begin The first index of previous segments.
- * @param {number} end The last index of previous segments.
- * @returns {CodePathSegment[]} New segments.
+ * Creates new segments in this context and does not append them to the end
+ * of the already existing `CodePathSegment`s specified by `startIndex` and
+ * `endIndex`. The `startIndex` and `endIndex` are only used to determine if
+ * the new segments should be reachable. If any of the segments in this range
+ * are reachable then the new segments are also reachable; otherwise, the new
+ * segments are unreachable.
+ * @param {number} startIndex The index of the first segment in the context
+ * that should be considered for reachability.
+ * @param {number} endIndex The index of the last segment in the context
+ * that should be considered for reachability.
+ * @returns {Array} An array of the newly created segments.
*/
- makeDisconnected(begin, end) {
- return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
+ makeDisconnected(startIndex, endIndex) {
+ return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected);
}
/**
- * Adds segments into this context.
- * The added segments become the head.
- * @param {CodePathSegment[]} segments Segments to add.
+ * Adds segments to the head of this context.
+ * @param {Array} segments The segments to add.
* @returns {void}
*/
add(segments) {
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
-
this.segmentsList.push(mergeExtraSegments(this, segments));
}
/**
- * Replaces the head segments with given segments.
+ * Replaces the head segments with the given segments.
* The current head segments are removed.
- * @param {CodePathSegment[]} segments Segments to add.
+ * @param {Array} replacementHeadSegments The new head segments.
* @returns {void}
*/
- replaceHead(segments) {
- assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
-
- this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
+ replaceHead(replacementHeadSegments) {
+ assert(
+ replacementHeadSegments.length >= this.count,
+ `${replacementHeadSegments.length} >= ${this.count}`
+ );
+ this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments));
}
/**
* Adds all segments of a given fork context into this context.
- * @param {ForkContext} context A fork context to add.
+ * @param {ForkContext} otherForkContext The fork context to add from.
* @returns {void}
*/
- addAll(context) {
- assert(context.count === this.count);
-
- const source = context.segmentsList;
-
- for (let i = 0; i < source.length; ++i) {
- this.segmentsList.push(source[i]);
- }
+ addAll(otherForkContext) {
+ assert(otherForkContext.count === this.count);
+ this.segmentsList.push(...otherForkContext.segmentsList);
}
/**
@@ -218,7 +316,8 @@ class ForkContext {
}
/**
- * Creates the root fork context.
+ * Creates a new root context, meaning that there are no parent
+ * fork contexts.
* @param {IdGenerator} idGenerator An identifier generator for segments.
* @returns {ForkContext} New fork context.
*/
@@ -233,14 +332,16 @@ class ForkContext {
/**
* Creates an empty fork context preceded by a given context.
* @param {ForkContext} parentContext The parent fork context.
- * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
+ * @param {boolean} shouldForkLeavingPath Indicates that we are inside of
+ * a `finally` block and should therefore fork the path that leaves
+ * `finally`.
* @returns {ForkContext} New fork context.
*/
- static newEmpty(parentContext, forkLeavingPath) {
+ static newEmpty(parentContext, shouldForkLeavingPath) {
return new ForkContext(
parentContext.idGenerator,
parentContext,
- (forkLeavingPath ? 2 : 1) * parentContext.count
+ (shouldForkLeavingPath ? 2 : 1) * parentContext.count
);
}
}
diff --git a/lib/linter/config-comment-parser.js b/lib/linter/config-comment-parser.js
index 9aab3c444585..cde261204f5e 100644
--- a/lib/linter/config-comment-parser.js
+++ b/lib/linter/config-comment-parser.js
@@ -139,7 +139,7 @@ module.exports = class ConfigCommentParser {
const items = {};
string.split(",").forEach(name => {
- const trimmedName = name.trim();
+ const trimmedName = name.trim().replace(/^(?['"]?)(?.*)\k$/us, "$");
if (trimmedName) {
items[trimmedName] = true;
diff --git a/lib/options.js b/lib/options.js
index 2bc4018afb5d..ae9a5d5552a2 100644
--- a/lib/options.js
+++ b/lib/options.js
@@ -55,6 +55,7 @@ const optionator = require("optionator");
* @property {string} [stdinFilename] Specify filename to process STDIN as
* @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 {string[]} _ Positional filenames or patterns
*/
@@ -139,6 +140,17 @@ module.exports = function(usingFlatConfig) {
};
}
+ let warnIgnoredFlag;
+
+ if (usingFlatConfig) {
+ warnIgnoredFlag = {
+ option: "warn-ignored",
+ type: "Boolean",
+ default: "true",
+ description: "Suppress warnings when the file list includes ignored files"
+ };
+ }
+
return optionator({
prepend: "eslint [options] file.js [file.js] [dir]",
defaults: {
@@ -349,6 +361,7 @@ module.exports = function(usingFlatConfig) {
default: "false",
description: "Exit with exit code 2 in case of fatal error"
},
+ warnIgnoredFlag,
{
option: "debug",
type: "Boolean",
diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js
index 27ca585e9958..c084c04c8eda 100644
--- a/lib/rules/logical-assignment-operators.js
+++ b/lib/rules/logical-assignment-operators.js
@@ -150,6 +150,31 @@ function isInsideWithBlock(node) {
return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
}
+/**
+ * Gets the leftmost operand of a consecutive logical expression.
+ * @param {SourceCode} sourceCode The ESLint source code object
+ * @param {LogicalExpression} node LogicalExpression
+ * @returns {Expression} Leftmost operand
+ */
+function getLeftmostOperand(sourceCode, node) {
+ let left = node.left;
+
+ while (left.type === "LogicalExpression" && left.operator === node.operator) {
+
+ if (astUtils.isParenthesised(sourceCode, left)) {
+
+ /*
+ * It should have associativity,
+ * but ignore it if use parentheses to make the evaluation order clear.
+ */
+ return left;
+ }
+ left = left.left;
+ }
+ return left;
+
+}
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -318,7 +343,10 @@ module.exports = {
// foo = foo || bar
"AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
- if (!astUtils.isSameReference(assignment.left, assignment.right.left)) {
+ const leftOperand = getLeftmostOperand(sourceCode, assignment.right);
+
+ if (!astUtils.isSameReference(assignment.left, leftOperand)
+ ) {
return;
}
@@ -342,10 +370,10 @@ module.exports = {
yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
// -> foo ||= bar
- const logicalOperatorToken = getOperatorToken(assignment.right);
+ const logicalOperatorToken = getOperatorToken(leftOperand.parent);
const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
- yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]);
+ yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]);
}
};
diff --git a/package.json b/package.json
index 989e3528a479..ece3957419ee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint",
- "version": "8.50.0",
+ "version": "8.51.0",
"author": "Nicholas C. Zakas ",
"description": "An AST-based pattern checker for JavaScript.",
"bin": {
@@ -63,7 +63,7 @@
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
- "@eslint/js": "8.50.0",
+ "@eslint/js": "8.51.0",
"@humanwhocodes/config-array": "^0.11.11",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
diff --git a/packages/js/package.json b/packages/js/package.json
index fc5fb50fcb9a..7e43db7f2e9a 100644
--- a/packages/js/package.json
+++ b/packages/js/package.json
@@ -1,6 +1,6 @@
{
"name": "@eslint/js",
- "version": "8.50.0",
+ "version": "8.51.0",
"description": "ESLint JavaScript language implementation",
"main": "./src/index.js",
"scripts": {},
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index b09496202b03..dca8955d0386 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -387,6 +387,36 @@ describe("bin/eslint.js", () => {
return Promise.all([exitCodeAssertion, outputAssertion]);
});
+ // https://github.com/eslint/eslint/issues/17560
+ describe("does not print duplicate errors in the event of a crash", () => {
+
+ it("when there is an invalid config read from a config file", () => {
+ const config = path.join(__dirname, "../fixtures/bin/eslint.config-invalid.js");
+ const child = runESLint(["--config", config, "conf", "tools"]);
+ const exitCodeAssertion = assertExitCode(child, 2);
+ const outputAssertion = getOutput(child).then(output => {
+
+ // The error text should appear exactly once in stderr
+ assert.strictEqual(output.stderr.match(/A config object is using the "globals" key/gu).length, 1);
+ });
+
+ return Promise.all([exitCodeAssertion, outputAssertion]);
+ });
+
+ it("when there is an error in the next tick", () => {
+ const config = path.join(__dirname, "../fixtures/bin/eslint.config-tick-throws.js");
+ const child = runESLint(["--config", config, "Makefile.js"]);
+ const exitCodeAssertion = assertExitCode(child, 2);
+ const outputAssertion = getOutput(child).then(output => {
+
+ // The error text should appear exactly once in stderr
+ assert.strictEqual(output.stderr.match(/test_error_stack/gu).length, 1);
+ });
+
+ return Promise.all([exitCodeAssertion, outputAssertion]);
+ });
+ });
+
it("prints the error message pointing to line of code", () => {
const invalidConfig = path.join(__dirname, "../fixtures/bin/eslint.config.js");
const child = runESLint(["--no-ignore", "-c", invalidConfig]);
diff --git a/tests/fixtures/bin/eslint.config-invalid.js b/tests/fixtures/bin/eslint.config-invalid.js
new file mode 100644
index 000000000000..6f68e1784199
--- /dev/null
+++ b/tests/fixtures/bin/eslint.config-invalid.js
@@ -0,0 +1,3 @@
+module.exports = [{
+ globals: {}
+}];
diff --git a/tests/fixtures/bin/eslint.config-tick-throws.js b/tests/fixtures/bin/eslint.config-tick-throws.js
new file mode 100644
index 000000000000..c72f86a840f7
--- /dev/null
+++ b/tests/fixtures/bin/eslint.config-tick-throws.js
@@ -0,0 +1,24 @@
+function throwError() {
+ const error = new Error();
+ error.stack = "test_error_stack";
+ throw error;
+}
+
+module.exports = [{
+ plugins: {
+ foo: {
+ rules: {
+ bar: {
+ create() {
+ process.nextTick(throwError);
+ process.nextTick(throwError);
+ return {};
+ }
+ }
+ }
+ }
+ },
+ rules: {
+ "foo/bar": "error"
+ }
+}];
diff --git a/tests/fixtures/code-path-analysis/assignment--nested-and-3.js b/tests/fixtures/code-path-analysis/assignment--nested-and-3.js
index 8bb52f022395..09ca58fade56 100644
--- a/tests/fixtures/code-path-analysis/assignment--nested-and-3.js
+++ b/tests/fixtures/code-path-analysis/assignment--nested-and-3.js
@@ -1,7 +1,8 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4;
-s1_1->s1_4;
-s1_2->s1_4->final;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
*/
(a &&= b) ?? c;
@@ -15,7 +16,8 @@ digraph {
s1_3[label="Identifier (c)"];
s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
initial->s1_1->s1_2->s1_3->s1_4;
- s1_1->s1_4;
- s1_2->s1_4->final;
+ s1_1->s1_3;
+ s1_2->s1_4;
+ s1_1->s1_4->final;
}
*/
diff --git a/tests/fixtures/code-path-analysis/logical--and-qq.js b/tests/fixtures/code-path-analysis/logical--and-qq.js
new file mode 100644
index 000000000000..5ce3853a240f
--- /dev/null
+++ b/tests/fixtures/code-path-analysis/logical--and-qq.js
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+(a && b) ?? c;
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (a)"];
+ s1_2[label="Identifier (b)\nLogicalExpression:exit"];
+ s1_3[label="Identifier (c)"];
+ s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->s1_2->s1_3->s1_4;
+ s1_1->s1_3;
+ s1_2->s1_4;
+ s1_1->s1_4->final;
+}*/
diff --git a/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js
index 4863ac81db3b..427cc22ec5a5 100644
--- a/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js
+++ b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js
@@ -1,8 +1,9 @@
/*expected
initial->s1_1->s1_2->s1_3->s1_4->s1_6;
-s1_1->s1_5->s1_6;
+s1_1->s1_3;
s1_2->s1_4;
-s1_3->s1_5;
+s1_3->s1_5->s1_6;
+s1_1->s1_5;
s1_2->s1_5;
s1_6->final;
*/
@@ -24,9 +25,10 @@ digraph {
s1_6[label="IfStatement:exit\nProgram:exit"];
s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
initial->s1_1->s1_2->s1_3->s1_4->s1_6;
- s1_1->s1_5->s1_6;
+ s1_1->s1_3;
s1_2->s1_4;
- s1_3->s1_5;
+ s1_3->s1_5->s1_6;
+ s1_1->s1_5;
s1_2->s1_5;
s1_6->final;
}
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index 15556e5cfd1e..ad0b8ad23272 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -801,6 +801,32 @@ describe("cli", () => {
assert.isFalse(log.info.called);
assert.strictEqual(exit, 0);
});
+
+ it(`should suppress the warning if --no-warn-ignored is passed with configType:${configType}`, async () => {
+ const options = useFlatConfig
+ ? `--config ${getFixturePath("eslint.config_with_ignores.js")}`
+ : `--ignore-path ${getFixturePath(".eslintignore")}`;
+ const filePath = getFixturePath("passing.js");
+ const exit = await cli.execute(`${options} --no-warn-ignored ${filePath}`, null, useFlatConfig);
+
+ assert.isFalse(log.info.called);
+
+ // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized.
+ assert.strictEqual(exit, useFlatConfig ? 0 : 2);
+ });
+
+ 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")}`
+ : `--ignore-path ${getFixturePath(".eslintignore")}`;
+ const filePath = getFixturePath("passing.js");
+ const exit = await cli.execute(`${options} --no-warn-ignored --stdin --stdin-filename ${filePath}`, "foo", useFlatConfig);
+
+ assert.isFalse(log.info.called);
+
+ // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized.
+ assert.strictEqual(exit, useFlatConfig ? 0 : 2);
+ });
});
describe("when given a pattern to ignore", () => {
diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js
index 358882374831..728b3e93785c 100644
--- a/tests/lib/config/flat-config-array.js
+++ b/tests/lib/config/flat-config-array.js
@@ -1726,6 +1726,17 @@ describe("FlatConfigArray", () => {
], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
});
+ it("should error when a string rule severity is not in lowercase", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ foo: "Error"
+ }
+ }
+ ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
+ });
+
it("should error when an invalid rule severity is set in an array", async () => {
await assertInvalidConfig([
diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js
index f67e62e5c63c..d20dce0c3195 100644
--- a/tests/lib/eslint/flat-eslint.js
+++ b/tests/lib/eslint/flat-eslint.js
@@ -211,7 +211,8 @@ describe("FlatESLint", () => {
overrideConfig: "",
overrideConfigFile: "",
plugins: "",
- reportUnusedDisableDirectives: ""
+ reportUnusedDisableDirectives: "",
+ warnIgnored: ""
}),
new RegExp(escapeStringRegExp([
"Invalid Options:",
@@ -229,7 +230,8 @@ describe("FlatESLint", () => {
"- 'overrideConfig' must be an object or null.",
"- 'overrideConfigFile' must be a non-empty string, null, or true.",
"- 'plugins' must be an object or null.",
- "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."
+ "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.",
+ "- 'warnIgnored' must be a boolean."
].join("\n")), "u")
);
});
@@ -369,7 +371,31 @@ describe("FlatESLint", () => {
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].filePath, getFixturePath("passing.js"));
assert.strictEqual(results[0].messages[0].severity, 1);
- assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.");
+ assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.");
+ assert.strictEqual(results[0].messages[0].output, void 0);
+ assert.strictEqual(results[0].errorCount, 0);
+ assert.strictEqual(results[0].warningCount, 1);
+ assert.strictEqual(results[0].fatalErrorCount, 0);
+ assert.strictEqual(results[0].fixableErrorCount, 0);
+ assert.strictEqual(results[0].fixableWarningCount, 0);
+ assert.strictEqual(results[0].usedDeprecatedRules.length, 0);
+ assert.strictEqual(results[0].suppressedMessages.length, 0);
+ });
+
+ it("should return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false, but lintText warnIgnored is true", async () => {
+ eslint = new FlatESLint({
+ cwd: getFixturePath(".."),
+ overrideConfigFile: "fixtures/eslint.config_with_ignores.js",
+ warnIgnored: false
+ });
+
+ const options = { filePath: "fixtures/passing.js", warnIgnored: true };
+ const results = await eslint.lintText("var bar = foo;", options);
+
+ assert.strictEqual(results.length, 1);
+ assert.strictEqual(results[0].filePath, getFixturePath("passing.js"));
+ assert.strictEqual(results[0].messages[0].severity, 1);
+ assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.");
assert.strictEqual(results[0].messages[0].output, void 0);
assert.strictEqual(results[0].errorCount, 0);
assert.strictEqual(results[0].warningCount, 1);
@@ -397,18 +423,31 @@ describe("FlatESLint", () => {
assert.strictEqual(results.length, 0);
});
- it("should suppress excluded file warnings by default", async () => {
+ it("should not return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false", async () => {
eslint = new FlatESLint({
cwd: getFixturePath(".."),
- overrideConfigFile: "fixtures/eslint.config_with_ignores.js"
+ overrideConfigFile: "fixtures/eslint.config_with_ignores.js",
+ warnIgnored: false
});
const options = { filePath: "fixtures/passing.js" };
const results = await eslint.lintText("var bar = foo;", options);
- // should not report anything because there are no errors
+ // should not report anything because the warning is suppressed
assert.strictEqual(results.length, 0);
});
+ it("should show excluded file warnings by default", async () => {
+ eslint = new FlatESLint({
+ cwd: getFixturePath(".."),
+ overrideConfigFile: "fixtures/eslint.config_with_ignores.js"
+ });
+ const options = { filePath: "fixtures/passing.js" };
+ const results = await eslint.lintText("var bar = foo;", options);
+
+ assert.strictEqual(results.length, 1);
+ assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.");
+ });
+
it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => {
eslint = new FlatESLint({
cwd: getFixturePath(".."),
@@ -685,7 +724,7 @@ describe("FlatESLint", () => {
ignore: false
});
const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true });
- const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.";
+ const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js"));
@@ -1311,7 +1350,7 @@ describe("FlatESLint", () => {
cwd: getFixturePath("cli-engine")
});
const results = await eslint.lintFiles(["node_modules/foo.js"]);
- const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.";
+ const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].errorCount, 0);
@@ -1329,7 +1368,7 @@ describe("FlatESLint", () => {
cwd: getFixturePath("cli-engine")
});
const results = await eslint.lintFiles(["nested_node_modules/subdir/node_modules/text.js"]);
- const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.";
+ const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].errorCount, 0);
@@ -1348,7 +1387,7 @@ describe("FlatESLint", () => {
ignorePatterns: ["*.js"]
});
const results = await eslint.lintFiles(["node_modules_cleaner.js"]);
- const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
+ const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].errorCount, 0);
@@ -1361,6 +1400,16 @@ describe("FlatESLint", () => {
assert.strictEqual(results[0].suppressedMessages.length, 0);
});
+ it("should suppress the warning when a file in the node_modules folder passed explicitly and warnIgnored is false", async () => {
+ eslint = new FlatESLint({
+ cwd: getFixturePath("cli-engine"),
+ warnIgnored: false
+ });
+ const results = await eslint.lintFiles(["node_modules/foo.js"]);
+
+ assert.strictEqual(results.length, 0);
+ });
+
it("should report on globs with explicit inclusion of dotfiles", async () => {
eslint = new FlatESLint({
cwd: getFixturePath("cli-engine"),
@@ -1483,7 +1532,7 @@ describe("FlatESLint", () => {
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].filePath, filePath);
assert.strictEqual(results[0].messages[0].severity, 1);
- assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.");
+ assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.");
assert.strictEqual(results[0].errorCount, 0);
assert.strictEqual(results[0].warningCount, 1);
assert.strictEqual(results[0].fatalErrorCount, 0);
@@ -1492,6 +1541,18 @@ describe("FlatESLint", () => {
assert.strictEqual(results[0].suppressedMessages.length, 0);
});
+ it("should suppress the warning when an explicitly given file is ignored and warnIgnored is false", async () => {
+ eslint = new FlatESLint({
+ overrideConfigFile: "eslint.config_with_ignores.js",
+ cwd: getFixturePath(),
+ warnIgnored: false
+ });
+ const filePath = getFixturePath("passing.js");
+ const results = await eslint.lintFiles([filePath]);
+
+ assert.strictEqual(results.length, 0);
+ });
+
it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => {
eslint = new FlatESLint({
overrideConfigFile: "eslint.config_with_ignores.js",
@@ -1503,7 +1564,7 @@ describe("FlatESLint", () => {
assert.strictEqual(results.length, 1);
assert.strictEqual(results[0].filePath, filePath);
assert.strictEqual(results[0].messages[0].severity, 1);
- assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.");
+ assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.");
assert.strictEqual(results[0].errorCount, 0);
assert.strictEqual(results[0].warningCount, 1);
assert.strictEqual(results[0].fatalErrorCount, 0);
@@ -5400,7 +5461,7 @@ describe("FlatESLint", () => {
{
ruleId: null,
fatal: false,
- message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.",
+ message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.",
severity: 1,
nodeType: null
}
diff --git a/tests/lib/linter/config-comment-parser.js b/tests/lib/linter/config-comment-parser.js
index e9f595d77504..39b06b13cf6e 100644
--- a/tests/lib/linter/config-comment-parser.js
+++ b/tests/lib/linter/config-comment-parser.js
@@ -224,6 +224,28 @@ describe("ConfigCommentParser", () => {
b: true
});
});
+
+ it("should parse list config with quoted items", () => {
+ const code = "'a', \"b\", 'c\", \"d'";
+ const result = commentParser.parseListConfig(code);
+
+ assert.deepStrictEqual(result, {
+ a: true,
+ b: true,
+ "\"d'": true, // This result is correct because used mismatched quotes.
+ "'c\"": true // This result is correct because used mismatched quotes.
+ });
+ });
+ it("should parse list config with spaced items", () => {
+ const code = " a b , 'c d' , \"e f\" ";
+ const result = commentParser.parseListConfig(code);
+
+ assert.deepStrictEqual(result, {
+ "a b": true,
+ "c d": true,
+ "e f": true
+ });
+ });
});
});
diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js
index 5b3d06826126..c8fd4134caa6 100644
--- a/tests/lib/linter/linter.js
+++ b/tests/lib/linter/linter.js
@@ -1467,6 +1467,32 @@ describe("Linter", () => {
linter.verify(code, config);
assert(spy && spy.calledOnce);
});
+
+ it("variables should be available in global scope with quoted items", () => {
+ const code = `/*${ESLINT_ENV} 'node'*/ function f() {} /*${ESLINT_ENV} "browser", "mocha"*/`;
+ const config = { rules: { checker: "error" } };
+ let spy;
+
+ linter.defineRule("checker", {
+ create(context) {
+ spy = sinon.spy(() => {
+ const scope = context.getScope(),
+ exports = getVariable(scope, "exports"),
+ window = getVariable(scope, "window"),
+ it = getVariable(scope, "it");
+
+ assert.strictEqual(exports.writeable, true);
+ assert.strictEqual(window.writeable, false);
+ assert.strictEqual(it.writeable, false);
+ });
+
+ return { Program: spy };
+ }
+ });
+
+ linter.verify(code, config);
+ assert(spy && spy.calledOnce);
+ });
});
describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => {
@@ -1906,6 +1932,22 @@ describe("Linter", () => {
assert.strictEqual(suppressedMessages.length, 0);
});
+ it("should enable rule configured using a string severity that contains uppercase letters", () => {
+ const code = "/*eslint no-alert: \"Error\"*/ alert('test');";
+ const config = { rules: {} };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "no-alert");
+ assert.strictEqual(messages[0].severity, 2);
+ assert.strictEqual(messages[0].message, "Unexpected alert.");
+ assert.include(messages[0].nodeType, "CallExpression");
+
+ assert.strictEqual(suppressedMessages.length, 0);
+ });
+
it("rules should not change initial config", () => {
const config = { rules: { strict: 2 } };
const codeA = "/*eslint strict: 0*/ function bar() { return 2; }";
@@ -2604,6 +2646,33 @@ describe("Linter", () => {
assert.strictEqual(suppressedMessages.length, 2);
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
});
+
+ it("should report a violation with quoted rule names in eslint-disable-line", () => {
+ const code = [
+ "alert('test'); // eslint-disable-line 'no-alert'",
+ "console.log('test');", // here
+ "alert('test'); // eslint-disable-line \"no-alert\""
+ ].join("\n");
+ const config = {
+ rules: {
+ "no-alert": 1,
+ "no-console": 1
+ }
+ };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "no-console");
+ assert.strictEqual(messages[0].line, 2);
+
+ assert.strictEqual(suppressedMessages.length, 2);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[0].line, 1);
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[1].line, 3);
+ });
});
describe("eslint-disable-next-line", () => {
@@ -2957,6 +3026,31 @@ describe("Linter", () => {
assert.strictEqual(suppressedMessages.length, 0);
});
+
+ it("should ignore violation of specified rule on next line with quoted rule names", () => {
+ const code = [
+ "// eslint-disable-next-line 'no-alert'",
+ "alert('test');",
+ "// eslint-disable-next-line \"no-alert\"",
+ "alert('test');",
+ "console.log('test');"
+ ].join("\n");
+ const config = {
+ rules: {
+ "no-alert": 1,
+ "no-console": 1
+ }
+ };
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "no-console");
+
+ assert.strictEqual(suppressedMessages.length, 2);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
+ });
});
});
@@ -3233,6 +3327,61 @@ describe("Linter", () => {
assert.strictEqual(suppressedMessages[2].ruleId, "no-console");
assert.strictEqual(suppressedMessages[2].line, 6);
});
+
+ it("should report a violation with quoted rule names in eslint-disable", () => {
+ const code = [
+ "/*eslint-disable 'no-alert' */",
+ "alert('test');",
+ "console.log('test');", // here
+ "/*eslint-enable */",
+ "/*eslint-disable \"no-console\" */",
+ "alert('test');", // here
+ "console.log('test');"
+ ].join("\n");
+ const config = { rules: { "no-alert": 1, "no-console": 1 } };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 2);
+ assert.strictEqual(messages[0].ruleId, "no-console");
+ assert.strictEqual(messages[1].ruleId, "no-alert");
+
+ assert.strictEqual(suppressedMessages.length, 2);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
+ });
+
+ it("should report a violation with quoted rule names in eslint-enable", () => {
+ const code = [
+ "/*eslint-disable no-alert, no-console */",
+ "alert('test');",
+ "console.log('test');",
+ "/*eslint-enable 'no-alert'*/",
+ "alert('test');", // here
+ "console.log('test');",
+ "/*eslint-enable \"no-console\"*/",
+ "console.log('test');" // here
+ ].join("\n");
+ const config = { rules: { "no-alert": 1, "no-console": 1 } };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 2);
+ assert.strictEqual(messages[0].ruleId, "no-alert");
+ assert.strictEqual(messages[0].line, 5);
+ assert.strictEqual(messages[1].ruleId, "no-console");
+ assert.strictEqual(messages[1].line, 8);
+
+ assert.strictEqual(suppressedMessages.length, 3);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[0].line, 2);
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
+ assert.strictEqual(suppressedMessages[1].line, 3);
+ assert.strictEqual(suppressedMessages[2].ruleId, "no-console");
+ assert.strictEqual(suppressedMessages[2].line, 6);
+ });
});
describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => {
@@ -4813,6 +4962,20 @@ var a = "test2";
output
);
});
+
+ // Test for quoted rule names
+ for (const testcaseForLiteral of [
+ { code: code.replace(/((?:un)?used[\w-]*)/gu, '"$1"'), output: output.replace(/((?:un)?used[\w-]*)/gu, '"$1"') },
+ { code: code.replace(/((?:un)?used[\w-]*)/gu, "'$1'"), output: output.replace(/((?:un)?used[\w-]*)/gu, "'$1'") }
+ ]) {
+ // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach()
+ it(testcaseForLiteral.code, () => {
+ assert.strictEqual(
+ linter.verifyAndFix(testcaseForLiteral.code, config).output,
+ testcaseForLiteral.output
+ );
+ });
+ }
}
});
});
@@ -12206,6 +12369,29 @@ describe("Linter with FlatConfigArray", () => {
assert.strictEqual(suppressedMessages.length, 0);
});
+ it("should report a violation when a rule is configured using a string severity that contains uppercase letters", () => {
+ const messages = linter.verify("/*eslint no-alert: \"Error\"*/ alert('test');", {});
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.deepStrictEqual(
+ messages,
+ [
+ {
+ severity: 2,
+ ruleId: "no-alert",
+ message: "Inline configuration for rule \"no-alert\" is invalid:\n\tExpected severity of \"off\", 0, \"warn\", 1, \"error\", or 2. You passed \"Error\".\n",
+ line: 1,
+ column: 1,
+ endLine: 1,
+ endColumn: 29,
+ nodeType: null
+ }
+ ]
+ );
+
+ assert.strictEqual(suppressedMessages.length, 0);
+ });
+
it("should report a violation when the config violates a rule's schema", () => {
const messages = linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {});
const suppressedMessages = linter.getSuppressedMessages();
@@ -13060,6 +13246,60 @@ var a = "test2";
assert.strictEqual(suppressedMessages[1].line, 3);
});
+ it("should report a violation with quoted rule names in eslint-disable", () => {
+ const code = [
+ "/*eslint-disable 'no-alert' */",
+ "alert('test');",
+ "console.log('test');", // here
+ "/*eslint-enable */",
+ "/*eslint-disable \"no-console\" */",
+ "alert('test');", // here
+ "console.log('test');"
+ ].join("\n");
+ const config = { rules: { "no-alert": 1, "no-console": 1 } };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 2);
+ assert.strictEqual(messages[0].ruleId, "no-console");
+ assert.strictEqual(messages[1].ruleId, "no-alert");
+
+ assert.strictEqual(suppressedMessages.length, 2);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
+ });
+
+ it("should report a violation with quoted rule names in eslint-enable", () => {
+ const code = [
+ "/*eslint-disable no-alert, no-console */",
+ "alert('test');",
+ "console.log('test');",
+ "/*eslint-enable 'no-alert'*/",
+ "alert('test');", // here
+ "console.log('test');",
+ "/*eslint-enable \"no-console\"*/",
+ "console.log('test');" // here
+ ].join("\n");
+ const config = { rules: { "no-alert": 1, "no-console": 1 } };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 2);
+ assert.strictEqual(messages[0].ruleId, "no-alert");
+ assert.strictEqual(messages[0].line, 5);
+ assert.strictEqual(messages[1].ruleId, "no-console");
+ assert.strictEqual(messages[1].line, 8);
+
+ assert.strictEqual(suppressedMessages.length, 3);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[0].line, 2);
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
+ assert.strictEqual(suppressedMessages[1].line, 3);
+ assert.strictEqual(suppressedMessages[2].ruleId, "no-console");
+ assert.strictEqual(suppressedMessages[2].line, 6);
+ });
});
describe("/*eslint-disable-line*/", () => {
@@ -13293,6 +13533,32 @@ var a = "test2";
assert.strictEqual(suppressedMessages.length, 5);
});
+ it("should report a violation with quoted rule names in eslint-disable-line", () => {
+ const code = [
+ "alert('test'); // eslint-disable-line 'no-alert'",
+ "console.log('test');", // here
+ "alert('test'); // eslint-disable-line \"no-alert\""
+ ].join("\n");
+ const config = {
+ rules: {
+ "no-alert": 1,
+ "no-console": 1
+ }
+ };
+
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "no-console");
+ assert.strictEqual(messages[0].line, 2);
+
+ assert.strictEqual(suppressedMessages.length, 2);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[0].line, 1);
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[1].line, 3);
+ });
});
describe("/*eslint-disable-next-line*/", () => {
@@ -13689,6 +13955,31 @@ var a = "test2";
assert.strictEqual(suppressedMessages.length, 0);
});
+
+ it("should ignore violation of specified rule on next line with quoted rule names", () => {
+ const code = [
+ "// eslint-disable-next-line 'no-alert'",
+ "alert('test');",
+ "// eslint-disable-next-line \"no-alert\"",
+ "alert('test');",
+ "console.log('test');"
+ ].join("\n");
+ const config = {
+ rules: {
+ "no-alert": 1,
+ "no-console": 1
+ }
+ };
+ const messages = linter.verify(code, config, filename);
+ const suppressedMessages = linter.getSuppressedMessages();
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "no-console");
+
+ assert.strictEqual(suppressedMessages.length, 2);
+ assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
+ assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
+ });
});
describe("descriptions in directive comments", () => {
@@ -15366,6 +15657,20 @@ var a = "test2";
output
);
});
+
+ // Test for quoted rule names
+ for (const testcaseForLiteral of [
+ { code: code.replace(/(test\/[\w-]+)/gu, '"$1"'), output: output.replace(/(test\/[\w-]+)/gu, '"$1"') },
+ { code: code.replace(/(test\/[\w-]+)/gu, "'$1'"), output: output.replace(/(test\/[\w-]+)/gu, "'$1'") }
+ ]) {
+ // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach()
+ it(testcaseForLiteral.code, () => {
+ assert.strictEqual(
+ linter.verifyAndFix(testcaseForLiteral.code, config).output,
+ testcaseForLiteral.output
+ );
+ });
+ }
}
});
});
@@ -15627,7 +15932,7 @@ var a = "test2";
const code = BROKEN_TEST_CODE;
it("should report a violation with a useful parse error prefix", () => {
- const messages = linter.verify(code);
+ const messages = linter.verify(code, {});
const suppressedMessages = linter.getSuppressedMessages();
assert.strictEqual(messages.length, 1);
@@ -15648,7 +15953,7 @@ var a = "test2";
" x++;",
"}"
];
- const messages = linter.verify(inValidCode.join("\n"));
+ const messages = linter.verify(inValidCode.join("\n"), {});
const suppressedMessages = linter.getSuppressedMessages();
assert.strictEqual(messages.length, 1);
@@ -16584,7 +16889,7 @@ var a = "test2";
});
it("should not crash when invalid parentheses syntax is encountered", () => {
- linter.verify("left = (aSize.width/2) - ()");
+ linter.verify("left = (aSize.width/2) - ()", {});
});
it("should not crash when let is used inside of switch case", () => {
diff --git a/tests/lib/options.js b/tests/lib/options.js
index d8f795b78a22..b663e8623e36 100644
--- a/tests/lib/options.js
+++ b/tests/lib/options.js
@@ -415,4 +415,18 @@ describe("options", () => {
});
});
+ describe("--no-warn-ignored", () => {
+ it("should return false when --no-warn-ignored is passed", () => {
+ const currentOptions = flatOptions.parse("--no-warn-ignored");
+
+ assert.isFalse(currentOptions.warnIgnored);
+ });
+
+ it("should return true when --warn-ignored is passed", () => {
+ const currentOptions = flatOptions.parse("--warn-ignored");
+
+ assert.isTrue(currentOptions.warnIgnored);
+ });
+ });
+
});
diff --git a/tests/lib/rules/logical-assignment-operators.js b/tests/lib/rules/logical-assignment-operators.js
index 36756815a90f..471416322d2b 100644
--- a/tests/lib/rules/logical-assignment-operators.js
+++ b/tests/lib/rules/logical-assignment-operators.js
@@ -354,6 +354,28 @@ ruleTester.run("logical-assignment-operators", rule, {
}, {
code: "a.b = a.b || c",
options: ["never"]
+ },
+
+ // 3 or more operands
+ {
+ code: "a = a && b || c",
+ options: ["always"]
+ },
+ {
+ code: "a = a && b && c || d",
+ options: ["always"]
+ },
+ {
+ code: "a = (a || b) || c", // Allow if parentheses are used.
+ options: ["always"]
+ },
+ {
+ code: "a = (a && b) && c", // Allow if parentheses are used.
+ options: ["always"]
+ },
+ {
+ code: "a = (a ?? b) ?? c", // Allow if parentheses are used.
+ options: ["always"]
}
],
invalid: [
@@ -1511,6 +1533,151 @@ ruleTester.run("logical-assignment-operators", rule, {
output: "(a.b.c ||= d) as number"
}]
}]
+ },
+
+ // 3 or more operands
+ {
+ code: "a = a || b || c",
+ output: "a ||= b || c",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a && b && c",
+ output: "a &&= b && c",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "&&=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a ?? b ?? c",
+ output: "a ??= b ?? c",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "??=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a || b && c",
+ output: "a ||= b && c",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a || b || c || d",
+ output: "a ||= b || c || d",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a && b && c && d",
+ output: "a &&= b && c && d",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "&&=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a ?? b ?? c ?? d",
+ output: "a ??= b ?? c ?? d",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "??=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a || b || c && d",
+ output: "a ||= b || c && d",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a || b && c || d",
+ output: "a ||= b && c || d",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = (a) || b || c",
+ output: "a ||= b || c",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = a || (b || c) || d",
+ output: "a ||= (b || c) || d",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = (a || b || c)",
+ output: "a ||= (b || c)",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
+ },
+ {
+ code: "a = ((a) || (b || c) || d)",
+ output: "a ||= ((b || c) || d)",
+ options: ["always"],
+ errors: [{
+ messageId: "assignment",
+ type: "AssignmentExpression",
+ data: { operator: "||=" },
+ suggestions: []
+ }]
}
]
});