Repository: eslint/eslint (js)

Issue:
Reevaluate the `meta.docs.suggestion` property
**The problem you want to solve.**

There is a rule property `meta.docs.suggestions` mentioned in the [Rule Basics](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) documentation that was added along with the [Suggestions API](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) / [Suggestions RFC](https://github.com/eslint/rfcs/pull/30):

> suggestion (boolean) specifies whether rules can return suggestions (defaults to false if omitted)

There are a few potential problems with this property:

1. It is missing from some of ESLint's examples / test cases depicting usage of the suggestions API (more discussion in: https://github.com/eslint/eslint/pull/14298).
2. Its name and location are inconsistent with a related property `meta.fixable`.
3. Its presence is not enforced, so humans and tooling cannot reliably use it for determining whether a rule provides suggestions (as opposed to `meta.fixable` whose presence is enforced for autofixable rules) (more discussion in: https://github.com/eslint/eslint/pull/14292).

Note that ESLint itself doesn't currently need to know which rules are suggestable (provide suggestions), whereas it does need to know which rules are fixable, so differences between `meta.docs.suggestions` and `meta.fixable` could be justified if we wanted to keep `meta.docs.suggestions` as an optional property (as opposed to `meta.fixable` which is a required property).

**Your take on the correct solution to the problem.**

There are several potential solutions here of varying size.

1. Keep `meta.docs.suggestion` as an optional property, fully document it, and add a lint rule [eslint-plugin/require-meta-docs-suggestion](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/pull/105) for those wishing to enforce its presence. 
    - Maintains the status quo, no disruption to anyone
    - The property would still be of limited usefulness as we can't assume plugin rules would provide it
2. Keep `meta.docs.suggestion`, and begin to enforce its presence as a required property for suggestable rules.  Implementation in https://github.com/eslint/eslint/pull/14292.
    - Ensures that humans and tooling have a reliable means of determining which rules are suggestable
    - But now there would be a noticeable naming/location inconsistency between this property and `meta.fixable` which would both be required properties
    - This is a breaking change that requires a one-line change in all rules that forgot to specify this property
3. Remove the `meta.docs.suggestion` property entirely and do not add a replacement for it.
    - Simplifies the API by removing a potentially unnecessary property
    - But now there's no recommended way to determine what rules provide suggestions
4. Rename the property to `meta.suggestable` for consistency with `meta.fixable`, and enforce its presence for suggestable rules.
     - Ensures that humans and tooling have a reliable means of determining which rules are suggestable
     - Cleans up the API by addressing the naming/location inconsistency with `meta.fixable` 
     - This is a breaking change that requires a one-line change in all suggestable rules

My preferred solution is 4 because it yields the most useful and consistent end-result, although this is also the most disruptive change, so I'm interested to hear what others prefer.

**Are you willing to submit a pull request to implement this change?**

Yes

Pull request description:
Breaking: Require `meta.hasSuggestions` for rules with suggestions
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [X] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[] Add autofixing to a rule
[ ] Add a CLI option
[X] Add something to the core
[ ] Other, please explain:

#### What changes did you make? (Give an overview)

Fixes #14312.

* Renames the old `meta.docs.suggestion` property to `meta.hasSuggestions` to indicate whether a rule provides suggestions (improved clarity and alignment with the `meta.fixable` property which is mandatory for fixable rules)
* Enforces the presence of the new property for rules that provide suggestions (the presence of the old property was not enforced)
* Enforces that the old property is no longer provided
* Updates core rules that provide suggestions to use the new property
* Adds the new property to test cases and documentation (some of which were missing it before)

This ensures we have a clear and reliable property to use for both humans and tooling to determine whether a rule provides suggestions.

Before merging:

- [ ] Double-check that all core rules with suggestions have been updated with the new property

Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index b405b82fc..9ebb5f6b3 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -73,6 +73,7 @@ module.exports = {
         "eslint-plugin/prefer-replace-text": "error",
         "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"],
         "eslint-plugin/require-meta-docs-description": "error",
+        "eslint-plugin/require-meta-has-suggestions": "error",
         "eslint-plugin/require-meta-schema": "error",
         "eslint-plugin/require-meta-type": "error",
         "eslint-plugin/test-case-property-ordering": "error",
diff --git a/Makefile.js b/Makefile.js
index 6d1ee3919..e84d21abf 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -198,7 +198,7 @@ function generateRuleIndexPage() {
                         description: rule.meta.docs.description,
                         recommended: rule.meta.docs.recommended || false,
                         fixable: !!rule.meta.fixable,
-                        hasSuggestions: !!rule.meta.docs.suggestion
+                        hasSuggestions: !!rule.meta.hasSuggestions
                     },
                     category = categoriesData.categories.find(c => c.name === rule.meta.docs.category);
 
@@ -671,7 +671,7 @@ target.gensite = function(prereleaseVersion) {
                 const rule = rules.get(ruleName);
                 const isRecommended = rule && rule.meta.docs.recommended;
                 const isFixable = rule && rule.meta.fixable;
-                const hasSuggestions = rule && rule.meta.docs.suggestion;
+                const hasSuggestions = rule && rule.meta.hasSuggestions;
 
                 // Incorporate the special portion into the documentation content
                 const textSplit = text.split("\n");
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 77e05da10..3da1731d5 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -62,7 +62,6 @@ The source file for a rule exports an object with the following properties.
     * `category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/)
     * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring/configuration-files.md#extending-configuration-files) enables the rule
     * `url` (string) specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations)
-    * `suggestion` (boolean) specifies whether rules can return suggestions (defaults to false if omitted)
 
     In a custom rule or plugin, you can omit `docs` or include any properties that you need in it.
 
@@ -70,6 +69,10 @@ The source file for a rule exports an object with the following properties.
 
     **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable.
 
+* `hasSuggestions` (boolean) specifies whether rules can return suggestions (defaults to `false` if omitted)
+
+     **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions.
+
 * `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules.md#configuring-rules)
 
 * `deprecated` (boolean) indicates whether the rule has been deprecated.  You may omit the `deprecated` property if the rule has not been deprecated.
@@ -376,6 +379,8 @@ context.report({
 {% endraw %}
 ```
 
+**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-basics) this property.
+
 Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or conform to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it.
 
 Best practices for suggestions:
@@ -397,7 +402,8 @@ module.exports = {
             unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
             removeEscape: "Remove the `\\`. This maintains the current functionality.",
             escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
-        }
+        },
+        hasSuggestions: true
     },
     create: function(context) {
         // ...
@@ -438,7 +444,8 @@ module.exports = {
         messages: {
             unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
             removeEscape: "Remove `\\` before {{character}}.",
-        }
+        },
+        hasSuggestions: true
     },
     create: function(context) {
         // ...
diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md
index 30647031e..4058cad3a 100644
--- a/docs/rules/no-unused-vars.md
+++ b/docs/rules/no-unused-vars.md
@@ -82,17 +82,15 @@ function getY([, y]) {
 
 ### exported
 
-In environments outside of CommonJS or ECMAScript modules, you may use `var` to create a global variable that may be used by other scripts. You can use the `/* exported variableName */` comment block to indicate that this variable is being exported and therefore should not be considered unused.
+In environments outside of CommonJS or ECMAScript modules, you may use `var` to create a global variable that may be used by other scripts. You can use the comment `/* exported variableName */` or `// exported variableName` to indicate that this variable is being exported and therefore should not be considered unused.
 
-Note that `/* exported */` has no effect for any of the following:
+Note that the comment has no effect for any of the following:
 
 * when the environment is `node` or `commonjs`
 * when `parserOptions.sourceType` is `module`
 * when `ecmaFeatures.globalReturn` is `true`
 
-The line comment `// exported variableName` will not work as `exported` is not line-specific.
-
-Examples of **correct** code for `/* exported variableName */` operation:
+Examples of **correct** code for `exported` operation:
 
 ```js
 /* exported global_var */
@@ -100,6 +98,12 @@ Examples of **correct** code for `/* exported variableName */` operation:
 var global_var = 42;
 ```
 
+```js
+// exported global_var
+
+var global_var = 42;
+```
+
 ## Options
 
 This rule takes one argument which can be a string or an object. The string settings are the same as those of the `vars` property (explained below).
diff --git a/docs/user-guide/configuring/language-options.md b/docs/user-guide/configuring/language-options.md
index 08b62aad5..43595c093 100644
--- a/docs/user-guide/configuring/language-options.md
+++ b/docs/user-guide/configuring/language-options.md
@@ -44,12 +44,16 @@ Environments can be specified inside of a file, in configuration files or using
 
 ### Using configuration comments
 
-To specify environments using a comment inside of your JavaScript file, use the following format:
+To specify environments using a line or block comment inside of your JavaScript file, use the following format:
 
 ```js
 /* eslint-env node, mocha */
 ```
 
+```js
+// eslint-env node, mocha
+```
+
 This enables Node.js and Mocha environments.
 
 ### Using configuration files
@@ -123,12 +127,16 @@ Some of ESLint's core rules rely on knowledge of the global variables available
 
 ### Using configuration comments
 
-To specify globals using a comment inside of your JavaScript file, use the following format:
+To specify globals using a line or block comment inside of your JavaScript file, use the following format:
 
 ```js
 /* global var1, var2 */
 ```
 
+```js
+// global var1, var2
+```
+
 This defines two global variables, `var1` and `var2`. If you want to optionally specify that these global variables can be written to (rather than only being read), then you can set each with a `"writable"` flag:
 
 ```js
diff --git a/docs/user-guide/configuring/rules.md b/docs/user-guide/configuring/rules.md
index 2274b98ed..5ee1cf7eb 100644
--- a/docs/user-guide/configuring/rules.md
+++ b/docs/user-guide/configuring/rules.md
@@ -14,12 +14,16 @@ ESLint comes with a large number of built-in rules and you can add more rules th
 
 ### Using configuration comments
 
-To configure rules inside of a file using configuration comments, use a comment in the following format:
+To configure rules inside of a file using configuration comments, use a line or block comment in the following format:
 
 ```js
 /* eslint eqeqeq: "off", curly: "error" */
 ```
 
+```js
+// eslint eqeqeq: "off", curly: "error"
+```
+
 In this example, [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq) is turned off and [`curly`](.https://eslint.org/docs/rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity:
 
 ```js
@@ -124,7 +128,7 @@ In these configuration files, the rule `plugin1/rule1` comes from the plugin nam
 
 ### Using configuration comments
 
-To temporarily disable rule warnings in your file, use block comments in the following format:
+To temporarily disable rule warnings in your file, use line or block comments in the following format:
 
 ```js
 /* eslint-disable */
@@ -134,6 +138,14 @@ alert('foo');
 /* eslint-enable */
 ```
 
+```js
+// eslint-disable
+
+alert('foo');
+
+// eslint-enable
+```
+
 You can also disable or enable warnings for specific rules:
 
 ```js
@@ -145,7 +157,7 @@ console.log('bar');
 /* eslint-enable no-alert, no-console */
 ```
 
-To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file:
+To disable rule warnings in an entire file, put a `/* eslint-disable */` or `// eslint-disable` at the top of the file:
 
 ```js
 /* eslint-disable */
diff --git a/lib/linter/linter.js b/lib/linter/linter.js
index 4e80926a8..51ba02aea 100644
--- a/lib/linter/linter.js
+++ b/lib/linter/linter.js
@@ -307,11 +307,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
             return;
         }
         const directiveText = match[1];
-        const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
-
-        if (comment.type === "Line" && !lineCommentSupported) {
-            return;
-        }
+        const mustBeOnSingleLine = /^eslint-disable-(next-)?line$/u.test(directiveText);
 
         if (warnInlineConfig) {
             const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`;
@@ -325,7 +321,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
             return;
         }
 
-        if (lineCommentSupported && comment.loc.start.line !== comment.loc.end.line) {
+        if (mustBeOnSingleLine && comment.loc.start.line !== comment.loc.end.line) {
             const message = `${directiveText} comment should not span multiple lines.`;
 
             problems.push(createLintingProblem({
@@ -452,7 +448,10 @@ function normalizeEcmaVersion(parser, ecmaVersion) {
     return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
 }
 
-const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gsu;
+const eslintEnvPatterns = [
+    /\/\*\s*eslint-env\s(.+?)\*\//gsu, // block comments
+    /\/\/[^\S\r\n\u2028\u2029]*eslint-env[^\S\r\n\u2028\u2029](.+)/gu // line comments
+];
 
 /**
  * Checks whether or not there is a comment which has "eslint-env *" in a given text.
@@ -462,15 +461,16 @@ const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gsu;
 function findEslintEnv(text) {
     let match, retv;
 
-    eslintEnvPattern.lastIndex = 0;
+    for (const eslintEnvPattern of eslintEnvPatterns) {
+        eslintEnvPattern.lastIndex = 0;
 
-    while ((match = eslintEnvPattern.exec(text)) !== null) {
-        retv = Object.assign(
-            retv || {},
-            commentParser.parseListConfig(stripDirectiveComment(match[1]))
-        );
+        while ((match = eslintEnvPattern.exec(text)) !== null) {
+            retv = Object.assign(
+                retv || {},
+                commentParser.parseListConfig(stripDirectiveComment(match[1]))
+            );
+        }
     }
-
     return retv;
 }
 
@@ -928,6 +928,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                         if (problem.fix && rule.meta && !rule.meta.fixable) {
                             throw new Error("Fixable rules should export a `meta.fixable` property.");
                         }
+                        if (problem.suggestions && !(rule.meta && rule.meta.hasSuggestions === true)) {
+                            if (rule.meta && rule.meta.docs && typeof rule.meta.docs.suggestion !== "undefined") {
+
+                                // Encourage migration from the former property name.
+                                throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
+                            }
+                            throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
+                        }
                         lintingProblems.push(problem);
                     }
                 }
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index 798c111ec..327dca7f8 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -123,7 +123,8 @@ module.exports = {
                         }
                     ]
                 }
-            ]
+            ],
+            additionalItems: false
         },
 
         messages: {
diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js
index 65ca32321..de94b5bea 100644
--- a/lib/rules/newline-before-return.js
+++ b/lib/rules/newline-before-return.js
@@ -134,7 +134,7 @@ module.exports = {
             if (tokenBefore) {
                 lineNumTokenBefore = tokenBefore.loc.end.line;
             } else {
-                lineNumTokenBefore = 0; // global return at beginning of script
+                lineNumTokenBefore = 0; // Global return at beginning of script
             }
 
             return lineNumTokenBefore;
diff --git a/lib/rules/no-empty-character-class.js b/lib/rules/no-empty-character-class.js
index 7dc219fe1..301f03bf3 100644
--- a/lib/rules/no-empty-character-class.js
+++ b/lib/rules/no-empty-character-class.js
@@ -12,16 +12,13 @@
 /*
  * plain-English description of the following regexp:
  * 0. `^` fix the match at the beginning of the string
- * 1. `\/`: the `/` that begins the regexp
- * 2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
- * 2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
- * 2.1. `\\.`: an escape sequence
- * 2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
- * 3. `\/` the `/` that ends the regexp
- * 4. `[gimuy]*`: optional regexp flags
- * 5. `$`: fix the match at the end of the string
+ * 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
+ * 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
+ * 1.1. `\\.`: an escape sequence
+ * 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
+ * 2. `$`: fix the match at the end of the string
  */
-const regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuys]*$/u;
+const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u;
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -46,18 +43,12 @@ module.exports = {
     },
 
     create(context) {
-        const sourceCode = context.getSourceCode();
-
         return {
-
-            Literal(node) {
-                const token = sourceCode.getFirstToken(node);
-
-                if (token.type === "RegularExpression" && !regex.test(token.value)) {
+            "Literal[regex]"(node) {
+                if (!regex.test(node.regex.pattern)) {
                     context.report({ node, messageId: "unexpected" });
                 }
             }
-
         };
 
     }
diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js
index 94ad5ba6d..e3c8682e6 100644
--- a/lib/rules/no-invalid-regexp.js
+++ b/lib/rules/no-invalid-regexp.js
@@ -10,7 +10,7 @@
 
 const RegExpValidator = require("regexpp").RegExpValidator;
 const validator = new RegExpValidator();
-const validFlags = /[gimuys]/gu;
+const validFlags = /[dgimsuy]/gu;
 const undefined1 = void 0;
 
 //------------------------------------------------------------------------------
diff --git a/lib/rules/no-nonoctal-decimal-escape.js b/lib/rules/no-nonoctal-decimal-escape.js
index 78cc39815..deaafac55 100644
--- a/lib/rules/no-nonoctal-decimal-escape.js
+++ b/lib/rules/no-nonoctal-decimal-escape.js
@@ -32,10 +32,11 @@ module.exports = {
             description: "disallow `\\8` and `\\9` escape sequences in string literals",
             category: "Best Practices",
             recommended: true,
-            url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape",
-            suggestion: true
+            url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape"
         },
 
+        hasSuggestions: true,
+
         schema: [],
 
         messages: {
diff --git a/lib/rules/no-unsafe-negation.js b/lib/rules/no-unsafe-negation.js
index a9c2ee74f..f1d5c9c4e 100644
--- a/lib/rules/no-unsafe-negation.js
+++ b/lib/rules/no-unsafe-negation.js
@@ -54,10 +54,11 @@ module.exports = {
             description: "disallow negating the left operand of relational operators",
             category: "Possible Errors",
             recommended: true,
-            url: "https://eslint.org/docs/rules/no-unsafe-negation",
-            suggestion: true
+            url: "https://eslint.org/docs/rules/no-unsafe-negation"
         },
 
+        hasSuggestions: true,
+
         schema: [
             {
                 type: "object",
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index 512c93a8b..436a75316 100644
--- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js
@@ -85,10 +85,11 @@ module.exports = {
             description: "disallow unnecessary escape characters",
             category: "Best Practices",
             recommended: true,
-            url: "https://eslint.org/docs/rules/no-useless-escape",
-            suggestion: true
+            url: "https://eslint.org/docs/rules/no-useless-escape"
         },
 
+        hasSuggestions: true,
+
         messages: {
             unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
             removeEscape: "Remove the `\\`. This maintains the current functionality.",
diff --git a/lib/rules/radix.js b/lib/rules/radix.js
index d1b67e5e2..f1a7c1af2 100644
--- a/lib/rules/radix.js
+++ b/lib/rules/radix.js
@@ -82,10 +82,11 @@ module.exports = {
             description: "enforce the consistent use of the radix argument when using `parseInt()`",
             category: "Best Practices",
             recommended: false,
-            url: "https://eslint.org/docs/rules/radix",
-            suggestion: true
+            url: "https://eslint.org/docs/rules/radix"
         },
 
+        hasSuggestions: true,
+
         schema: [
             {
                 enum: ["always", "as-needed"]
diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js
index 478f6c581..abb9c2c7e 100644
--- a/lib/rules/utils/ast-utils.js
+++ b/lib/rules/utils/ast-utils.js
@@ -875,14 +875,7 @@ module.exports = {
     isDirectiveComment(node) {
         const comment = node.value.trim();
 
-        return (
-            node.type === "Line" && comment.indexOf("eslint-") === 0 ||
-            node.type === "Block" && (
-                comment.indexOf("global ") === 0 ||
-                comment.indexOf("eslint ") === 0 ||
-                comment.indexOf("eslint-") === 0
-            )
-        );
+        return /^(?:eslint[- ]|(?:globals?|exported) )/u.test(comment);
     },
 
     /**
diff --git a/package.json b/package.json
index 1981419c5..593bebf07 100644
--- a/package.json
+++ b/package.json
@@ -75,7 +75,7 @@
     "natural-compare": "^1.4.0",
     "optionator": "^0.9.1",
     "progress": "^2.0.0",
-    "regexpp": "^3.1.0",
+    "regexpp": "^3.2.0",
     "semver": "^7.2.1",
     "strip-ansi": "^6.0.0",
     "strip-json-comments": "^3.1.0",


Test patch:
diff --git a/tests/fixtures/testers/rule-tester/suggestions.js b/tests/fixtures/testers/rule-tester/suggestions.js
index df97e6dd1..ecedde046 100644
--- a/tests/fixtures/testers/rule-tester/suggestions.js
+++ b/tests/fixtures/testers/rule-tester/suggestions.js
@@ -1,6 +1,7 @@
 "use strict";
 
 module.exports.basic = {
+    meta: { hasSuggestions: true },
     create(context) {
         return {
             Identifier(node) {
@@ -25,7 +26,8 @@ module.exports.withMessageIds = {
             avoidFoo: "Avoid using identifiers named '{{ name }}'.",
             unused: "An unused key",
             renameFoo: "Rename identifier 'foo' to '{{ newName }}'"
-        }
+        },
+        hasSuggestions: true
     },
     create(context) {
         return {
@@ -57,4 +59,16 @@ module.exports.withMessageIds = {
     }
 };
 
-
+module.exports.withoutHasSuggestionsProperty = {
+    create(context) {
+        return {
+            Identifier(node) {
+                context.report({
+                    node,
+                    message: "some message",
+                    suggest: [{ desc: "some suggestion", fix: fixer => fixer.replaceText(node, 'bar') }]
+                });
+            }
+        };
+    }
+};
diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js
index 522f9b197..b2724ae20 100644
--- a/tests/lib/linter/linter.js
+++ b/tests/lib/linter/linter.js
@@ -23,6 +23,7 @@ const { Linter } = require("../../../lib/linter");
 
 const TEST_CODE = "var answer = 6 * 7;",
     BROKEN_TEST_CODE = "var;";
+const linebreaks = ["\n", "\r\n", "\r", "\u2028", "\u2029"];
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -1360,15 +1361,16 @@ describe("Linter", () => {
     describe("when evaluating code containing a line comment", () => {
         const code = "//global a \n function f() {}";
 
-        it("should not introduce a global variable", () => {
+        it("should introduce a global variable", () => {
             const config = { rules: { checker: "error" } };
             let spy;
 
             linter.defineRule("checker", context => {
                 spy = sinon.spy(() => {
                     const scope = context.getScope();
+                    const a = getVariable(scope, "a");
 
-                    assert.strictEqual(getVariable(scope, "a"), null);
+                    assert.strictEqual(a.eslintExplicitGlobal, true);
                 });
 
                 return { Program: spy };
@@ -4799,21 +4801,24 @@ var a = "test2";
 
     describe("suggestions", () => {
         it("provides suggestion information for tools to use", () => {
-            linter.defineRule("rule-with-suggestions", context => ({
-                Program(node) {
-                    context.report({
-                        node,
-                        message: "Incorrect spacing",
-                        suggest: [{
-                            desc: "Insert space at the beginning",
-                            fix: fixer => fixer.insertTextBefore(node, " ")
-                        }, {
-                            desc: "Insert space at the end",
-                            fix: fixer => fixer.insertTextAfter(node, " ")
-                        }]
-                    });
-                }
-            }));
+            linter.defineRule("rule-with-suggestions", {
+                meta: { hasSuggestions: true },
+                create: context => ({
+                    Program(node) {
+                        context.report({
+                            node,
+                            message: "Incorrect spacing",
+                            suggest: [{
+                                desc: "Insert space at the beginning",
+                                fix: fixer => fixer.insertTextBefore(node, " ")
+                            }, {
+                                desc: "Insert space at the end",
+                                fix: fixer => fixer.insertTextAfter(node, " ")
+                            }]
+                        });
+                    }
+                })
+            });
 
             const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } });
 
@@ -4838,7 +4843,8 @@ var a = "test2";
                     messages: {
                         suggestion1: "Insert space at the beginning",
                         suggestion2: "Insert space at the end"
-                    }
+                    },
+                    hasSuggestions: true
                 },
                 create: context => ({
                     Program(node) {
@@ -4875,6 +4881,44 @@ var a = "test2";
                 }
             }]);
         });
+
+        it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => {
+            linter.defineRule("rule-with-suggestions", {
+                meta: { docs: {}, schema: [] },
+                create: context => ({
+                    Program(node) {
+                        context.report({
+                            node,
+                            message: "hello world",
+                            suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }]
+                        });
+                    }
+                })
+            });
+
+            assert.throws(() => {
+                linter.verify("0", { rules: { "rule-with-suggestions": "error" } });
+            }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
+        });
+
+        it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => {
+            linter.defineRule("rule-with-meta-docs-suggestion", {
+                meta: { docs: { suggestion: true }, schema: [] },
+                create: context => ({
+                    Program(node) {
+                        context.report({
+                            node,
+                            message: "hello world",
+                            suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }]
+                        });
+                    }
+                })
+            });
+
+            assert.throws(() => {
+                linter.verify("0", { rules: { "rule-with-meta-docs-suggestion": "error" } });
+            }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
+        });
     });
 
     describe("mutability", () => {
@@ -5521,4 +5565,122 @@ var a = "test2";
             assert.strictEqual(messages.length, 0);
         });
     });
+
+    // https://github.com/eslint/eslint/issues/14575
+    describe("directives in line comments", () => {
+        let messages;
+
+        it("//eslint-disable", () => {
+            const codes = [
+                ...linebreaks.map(linebreak => `//eslint-disable no-debugger${linebreak}debugger;`),
+                ...linebreaks.map(linebreak => `// eslint-disable no-debugger${linebreak}debugger;`)
+            ];
+
+            for (const code of codes) {
+                messages = linter.verify(code, { rules: { "no-debugger": 2 } });
+                assert.strictEqual(messages.length, 0);
+
+                messages = linter.verify(code, { rules: { "no-debugger": 0 } });
+                assert.strictEqual(messages.length, 0);
+            }
+        });
+
+        it("//eslint-enable", () => {
+            const codes = [
+                ...linebreaks.map(linebreak => `//eslint-disable no-debugger${linebreak}//eslint-enable no-debugger${linebreak}debugger;`),
+                ...linebreaks.map(linebreak => `// eslint-disable no-debugger${linebreak}//eslint-enable no-debugger${linebreak}debugger;`)
+            ];
+
+            for (const code of codes) {
+                messages = linter.verify(code, { rules: { "no-debugger": 2 } });
+                assert.strictEqual(messages.length, 1);
+            }
+        });
+
+        it("//eslint", () => {
+            const codes = [
+                ...linebreaks.map(linebreak => `//eslint no-debugger:'error'${linebreak}debugger;`),
+                ...linebreaks.map(linebreak => `// eslint no-debugger:'error'${linebreak}debugger;`)
+            ];
+
+            for (const code of codes) {
+                messages = linter.verify(code, { rules: { "no-debugger": 0 } });
+                assert.strictEqual(messages.length, 1);
+                messages = linter.verify(code, { rules: { "no-debugger": 2 } });
+                assert.strictEqual(messages.length, 1);
+            }
+        });
+
+        it("//global(s)", () => {
+            const config = { rules: { "no-undef": 2 } };
+            const codes = [
+                ...linebreaks.map(linebreak => `//globals foo: true${linebreak}foo;`),
+                ...linebreaks.map(linebreak => `// globals foo: true${linebreak}foo;`),
+                ...linebreaks.map(linebreak => `//global foo: true${linebreak}foo;`),
+                ...linebreaks.map(linebreak => `// global foo: true${linebreak}foo;`)
+            ];
+
+            for (const code of codes) {
+                messages = linter.verify(code, config, filename);
+                assert.strictEqual(messages.length, 0);
+            }
+        });
+        it("//exported", () => {
+            const codes = [
+                ...linebreaks.map(linebreak => `//exported foo${linebreak}var foo = 0;`),
+                ...linebreaks.map(linebreak => `// exported foo${linebreak}var foo = 0;`)
+            ];
+            const config = { rules: { "no-unused-vars": 2 } };
+
+            for (const code of codes) {
+                messages = linter.verify(code, config, filename);
+                assert.strictEqual(messages.length, 0);
+            }
+        });
+
+        describe("//eslint-env", () => {
+            const config = { rules: { "no-undef": 2 } };
+
+            it("enable one env with different line breaks", () => {
+                const codes = [
+                    ...linebreaks.map(linebreak => `//${ESLINT_ENV} browser${linebreak}window;`),
+                    ...linebreaks.map(linebreak => `//  ${ESLINT_ENV}  browser${linebreak}window;`),
+                    `window;//${ESLINT_ENV} browser` // no linebreaks
+                ];
+
+                for (const code of codes) {
+                    messages = linter.verify(code, config, filename);
+                    assert.strictEqual(messages.length, 0);
+                }
+            });
+
+            it("multiple envs enabled with different line breaks", () => {
+                const codes = [
+                    ...linebreaks.map(linebreak => `//${ESLINT_ENV} browser,es6${linebreak}window;Promise;`),
+                    ...linebreaks.map(linebreak => `// ${ESLINT_ENV} browser,es6${linebreak}window;Promise;`),
+                    ...linebreaks.map(linebreak => `//${ESLINT_ENV} browser${linebreak}//${ESLINT_ENV} es6${linebreak}window;Promise;`),
+                    ...linebreaks.map(linebreak => `// ${ESLINT_ENV} browser${linebreak}//${ESLINT_ENV} es6${linebreak}window;Promise;`)
+                ];
+
+                for (const code of codes) {
+                    messages = linter.verify(code, config, filename);
+                    assert.strictEqual(messages.length, 0);
+                }
+            });
+
+            it("no env enabled with different linebreaks", () => {
+                const codes = [
+                    ...linebreaks.map(linebreak => `//${ESLINT_ENV}${linebreak}browser${linebreak}window;`),
+                    ...linebreaks.map(linebreak => `// ${ESLINT_ENV}${linebreak}browser${linebreak}window;`),
+                    ...linebreaks.map(linebreak => `//${ESLINT_ENV}browser${linebreak}window;window;`),
+                    ...linebreaks.map(linebreak => `// ${ESLINT_ENV}browser${linebreak}window;window;`)
+                ];
+
+                for (const code of codes) {
+                    messages = linter.verify(code, config, filename);
+                    assert.strictEqual(messages.length, 2);
+                }
+            });
+        });
+    });
 });
diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js
index 71225611d..3eef79823 100644
--- a/tests/lib/rule-tester/rule-tester.js
+++ b/tests/lib/rule-tester/rule-tester.js
@@ -2279,6 +2279,17 @@ describe("RuleTester", () => {
                 });
             }, /Invalid suggestion property name 'outpt'/u);
         });
+
+        it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => {
+            assert.throws(() => {
+                ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, {
+                    valid: [],
+                    invalid: [
+                        { code: "var foo = bar;", output: "5", errors: 1 }
+                    ]
+                });
+            }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
+        });
     });
 
     describe("naming test cases", () => {
diff --git a/tests/lib/rules/id-blacklist.js b/tests/lib/rules/id-blacklist.js
index 4d13459ac..a2f450798 100644
--- a/tests/lib/rules/id-blacklist.js
+++ b/tests/lib/rules/id-blacklist.js
@@ -1010,7 +1010,7 @@ ruleTester.run("id-blacklist", rule, {
             ]
         },
 
-        // globals declared in the given source code are not excluded from consideration
+        // Globals declared in the given source code are not excluded from consideration
         {
             code: "const foo = 1; bar = foo;",
             options: ["foo"],
diff --git a/tests/lib/rules/id-denylist.js b/tests/lib/rules/id-denylist.js
index 4e8248399..3d8f1f4d4 100644
--- a/tests/lib/rules/id-denylist.js
+++ b/tests/lib/rules/id-denylist.js
@@ -1022,7 +1022,7 @@ ruleTester.run("id-denylist", rule, {
             ]
         },
 
-        // globals declared in the given source code are not excluded from consideration
+        // Globals declared in the given source code are not excluded from consideration
         {
             code: "const foo = 1; bar = foo;",
             options: ["foo"],
diff --git a/tests/lib/rules/no-empty-character-class.js b/tests/lib/rules/no-empty-character-class.js
index 1421c2ad6..fd4cef8ed 100644
--- a/tests/lib/rules/no-empty-character-class.js
+++ b/tests/lib/rules/no-empty-character-class.js
@@ -32,6 +32,7 @@ ruleTester.run("no-empty-character-class", rule, {
         "var foo = /\\s*:\\s*/gim;",
         { code: "var foo = /[\\]]/uy;", parserOptions: { ecmaVersion: 6 } },
         { code: "var foo = /[\\]]/s;", parserOptions: { ecmaVersion: 2018 } },
+        { code: "var foo = /[\\]]/d;", parserOptions: { ecmaVersion: 2022 } },
         "var foo = /\\[]/"
     ],
     invalid: [
@@ -41,6 +42,7 @@ ruleTester.run("no-empty-character-class", rule, {
         { code: "if (/^abc[]/.test(foo)) {}", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "var foo = /[]]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "var foo = /\\[[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
-        { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }
+        { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] }
     ]
 });
diff --git a/tests/lib/rules/no-inline-comments.js b/tests/lib/rules/no-inline-comments.js
index 463c86eea..a055c1bc2 100644
--- a/tests/lib/rules/no-inline-comments.js
+++ b/tests/lib/rules/no-inline-comments.js
@@ -40,6 +40,16 @@ ruleTester.run("no-inline-comments", rule, {
         "var a = 1; // eslint-disable-line no-debugger",
         "var a = 1; /* eslint-disable-line no-debugger */",
 
+        // https://github.com/eslint/eslint/issues/14575
+        "var i;//eslint no-debugger:2",
+        "var i;// eslint no-debugger:2",
+        "var i;//eslint-disable no-debugger",
+        "var i;// eslint-disable no-debugger",
+        "var i;//eslint-enable no-debugger",
+        "var i;// eslint-enable no-debugger",
+        "var i;//global foo",
+        "var i;// global foo",
+
         // JSX exception
         `var a = (
             <div>
diff --git a/tests/lib/rules/no-invalid-regexp.js b/tests/lib/rules/no-invalid-regexp.js
index 6484db816..f1f2b4044 100644
--- a/tests/lib/rules/no-invalid-regexp.js
+++ b/tests/lib/rules/no-invalid-regexp.js
@@ -59,6 +59,9 @@ ruleTester.run("no-invalid-regexp", rule, {
         "new RegExp('(?<𝒜>.)', 'g');",
         "new RegExp('\\\\p{Script=Nandinagari}', 'u');",
 
+        // ES2022
+        "new RegExp('a+(?<Z>z)?', 'd')",
+
         // allowConstructorFlags
         {
             code: "new RegExp('.', 'g')",
diff --git a/tests/lib/rules/no-promise-executor-return.js b/tests/lib/rules/no-promise-executor-return.js
index a24629b6a..20cdf300a 100644
--- a/tests/lib/rules/no-promise-executor-return.js
+++ b/tests/lib/rules/no-promise-executor-return.js
@@ -86,7 +86,7 @@ ruleTester.run("no-promise-executor-return", rule, {
         "new Promise(foo, (resolve, reject) => { return 1; });",
         "new Promise(foo, (resolve, reject) => 1);",
 
-        // global Promise doesn't exist
+        // Global Promise doesn't exist
         "/* globals Promise:off */ new Promise(function (resolve, reject) { return 1; });",
         {
             code: "new Promise((resolve, reject) => { return 1; });",
@@ -97,7 +97,7 @@ ruleTester.run("no-promise-executor-return", rule, {
             env: { es6: false }
         },
 
-        // global Promise is shadowed
+        // Global Promise is shadowed
         "let Promise; new Promise(function (resolve, reject) { return 1; });",
         "function f() { new Promise((resolve, reject) => { return 1; }); var Promise; }",
         "function f(Promise) { new Promise((resolve, reject) => 1); }",
diff --git a/tests/lib/rules/no-setter-return.js b/tests/lib/rules/no-setter-return.js
index ab5b196b9..366e65c2c 100644
--- a/tests/lib/rules/no-setter-return.js
+++ b/tests/lib/rules/no-setter-return.js
@@ -225,7 +225,7 @@ ruleTester.run("no-setter-return", rule, {
         },
         "object.create(foo, { bar: { set: function(val) { return 1; } } })",
 
-        // global object doesn't exist
+        // Global object doesn't exist
         "Reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return 1; } } })",
         "/* globals Object:off */ Object.defineProperty(foo, 'bar', { set(val) { return 1; } })",
         {
@@ -233,7 +233,7 @@ ruleTester.run("no-setter-return", rule, {
             globals: { Object: "off" }
         },
 
-        // global object is shadowed
+        // Global object is shadowed
         "let Object; Object.defineProperty(foo, 'bar', { set(val) { return 1; } })",
         {
             code: "function f() { Reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return 1; } } }); var Reflect;}",
diff --git a/tests/lib/rules/no-warning-comments.js b/tests/lib/rules/no-warning-comments.js
index c2689defa..b465de9a0 100644
--- a/tests/lib/rules/no-warning-comments.js
+++ b/tests/lib/rules/no-warning-comments.js
@@ -37,7 +37,10 @@ ruleTester.run("no-warning-comments", rule, {
         { code: "// special regex characters don't cause problems", options: [{ terms: ["[aeiou]"], location: "anywhere" }] },
         "/*eslint no-warning-comments: [2, { \"terms\": [\"todo\", \"fixme\", \"any other term\"], \"location\": \"anywhere\" }]*/\n\nvar x = 10;\n",
         { code: "/*eslint no-warning-comments: [2, { \"terms\": [\"todo\", \"fixme\", \"any other term\"], \"location\": \"anywhere\" }]*/\n\nvar x = 10;\n", options: [{ location: "anywhere" }] },
-        { code: "foo", options: [{ terms: ["foo-bar"] }] }
+        { code: "foo", options: [{ terms: ["foo-bar"] }] },
+
+        // https://github.com/eslint/eslint/issues/14575
+        "//eslint no-warning-comments: [2, {terms: [\"eslint\"]}]"
     ],
     invalid: [
         {
diff --git a/tests/lib/rules/require-await.js b/tests/lib/rules/require-await.js
index 5ed2fe093..49bfc0684 100644
--- a/tests/lib/rules/require-await.js
+++ b/tests/lib/rules/require-await.js
@@ -43,7 +43,7 @@ ruleTester.run("require-await", rule, {
         // for-await-of
         "async function foo() { for await (x of xs); }",
 
-        // global await
+        // Global await
         {
             code: "await foo()",
             parser: require.resolve("../../fixtures/parsers/typescript-parsers/global-await")
diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js
index f69fbface..7e1365aa7 100644
--- a/tests/lib/rules/utils/ast-utils.js
+++ b/tests/lib/rules/utils/ast-utils.js
@@ -206,7 +206,12 @@ describe("ast-utils", () => {
                 "// lalala I'm a normal comment",
                 "// trying to confuse eslint ",
                 "//trying to confuse eslint-directive-detection",
-                "//eslint is awesome"
+                "//xxeslint-enable foo",
+                "//xxeslint-disable foo",
+                "//xxeslint foo: 2",
+                "//xxglobal foo",
+                "//xxglobals foo",
+                "//xxexported foo"
             ].join("\n");
             const ast = espree.parse(code, ESPREE_CONFIG);
             const sourceCode = new SourceCode(code, ast);
@@ -234,7 +239,13 @@ describe("ast-utils", () => {
                 "// eslint-disable-line no-undef",
                 "// eslint-secret-directive 4 8 15 16 23 42   ",
                 "// eslint-directive-without-argument",
-                "//eslint-directive-without-padding"
+                "//eslint-directive-without-padding",
+
+                // https://github.com/eslint/eslint/issues/14575
+                "//eslint foo:0",
+                "//global foo",
+                "//eslint-enable foo",
+                "//eslint-disable foo"
             ].join("\n");
             const ast = espree.parse(code, ESPREE_CONFIG);
             const sourceCode = new SourceCode(code, ast);