]> git.proxmox.com Git - pve-eslint.git/blobdiff - eslint/lib/linter/linter.js
import 8.23.1 source
[pve-eslint.git] / eslint / lib / linter / linter.js
index 1d021d1e82e7fb1a62a72f63219554b9ff11af15..a29ce9237928e9972317f885c817c289bd30c2df 100644 (file)
@@ -15,12 +15,16 @@ const
     eslintScope = require("eslint-scope"),
     evk = require("eslint-visitor-keys"),
     espree = require("espree"),
-    lodash = require("lodash"),
-    BuiltInEnvironments = require("../../conf/environments"),
+    merge = require("lodash.merge"),
     pkg = require("../../package.json"),
     astUtils = require("../shared/ast-utils"),
-    ConfigOps = require("../shared/config-ops"),
-    validator = require("../shared/config-validator"),
+    {
+        Legacy: {
+            ConfigOps,
+            ConfigValidator,
+            environments: BuiltInEnvironments
+        }
+    } = require("@eslint/eslintrc/universal"),
     Traverser = require("../shared/traverser"),
     { SourceCode } = require("../source-code"),
     CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
@@ -33,38 +37,48 @@ const
     SourceCodeFixer = require("./source-code-fixer"),
     timing = require("./timing"),
     ruleReplacements = require("../../conf/replacements.json");
+const { getRuleFromConfig } = require("../config/flat-config-helpers");
+const { FlatConfigArray } = require("../config/flat-config-array");
 
 const debug = require("debug")("eslint:linter");
 const MAX_AUTOFIX_PASSES = 10;
 const DEFAULT_PARSER_NAME = "espree";
+const DEFAULT_ECMA_VERSION = 5;
 const commentParser = new ConfigCommentParser();
 const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
+const parserSymbol = Symbol.for("eslint.RuleTester.parser");
+const globals = require("../../conf/globals");
 
 //------------------------------------------------------------------------------
 // Typedefs
 //------------------------------------------------------------------------------
 
-/** @typedef {InstanceType<import("../cli-engine/config-array")["ConfigArray"]>} ConfigArray */
-/** @typedef {InstanceType<import("../cli-engine/config-array")["ExtractedConfig"]>} ExtractedConfig */
+/** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
+/** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
 /** @typedef {import("../shared/types").ConfigData} ConfigData */
 /** @typedef {import("../shared/types").Environment} Environment */
 /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
 /** @typedef {import("../shared/types").LintMessage} LintMessage */
+/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
 /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
+/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
 /** @typedef {import("../shared/types").Processor} Processor */
 /** @typedef {import("../shared/types").Rule} Rule */
 
+/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
 /**
  * @template T
  * @typedef {{ [P in keyof T]-?: T[P] }} Required
  */
+/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
 
 /**
  * @typedef {Object} DisableDirective
- * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type
- * @property {number} line
- * @property {number} column
- * @property {(string|null)} ruleId
+ * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type Type of directive
+ * @property {number} line The line number
+ * @property {number} column The column number
+ * @property {(string|null)} ruleId The rule ID
+ * @property {string} justification The justification of directive
  */
 
 /**
@@ -72,6 +86,7 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
  * @typedef {Object} LinterInternalSlots
  * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used.
  * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
+ * @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced.
  * @property {Map<string, Parser>} parserMap The loaded parsers.
  * @property {Rules} ruleMap The loaded rules.
  */
@@ -92,12 +107,12 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
  * @typedef {Object} ProcessorOptions
  * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the
  *      predicate function that selects adopt code blocks.
- * @property {Processor["postprocess"]} [postprocess] postprocessor for report
+ * @property {Processor.postprocess} [postprocess] postprocessor for report
  *      messages. If provided, this should accept an array of the message lists
  *      for each code block returned from the preprocessor, apply a mapping to
  *      the messages as appropriate, and return a one-dimensional array of
  *      messages.
- * @property {Processor["preprocess"]} [preprocess] preprocessor for source text.
+ * @property {Processor.preprocess} [preprocess] preprocessor for source text.
  *      If provided, this should accept a string of source text, and return an
  *      array of code blocks to lint.
  */
@@ -118,6 +133,38 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
 // Helpers
 //------------------------------------------------------------------------------
 
+/**
+ * Determines if a given object is Espree.
+ * @param {Object} parser The parser to check.
+ * @returns {boolean} True if the parser is Espree or false if not.
+ */
+function isEspree(parser) {
+    return !!(parser === espree || parser[parserSymbol] === espree);
+}
+
+/**
+ * Retrieves globals for the given ecmaVersion.
+ * @param {number} ecmaVersion The version to retrieve globals for.
+ * @returns {Object} The globals for the given ecmaVersion.
+ */
+function getGlobalsForEcmaVersion(ecmaVersion) {
+
+    switch (ecmaVersion) {
+        case 3:
+            return globals.es3;
+
+        case 5:
+            return globals.es5;
+
+        default:
+            if (ecmaVersion < 2015) {
+                return globals[`es${ecmaVersion + 2009}`];
+            }
+
+            return globals[`es${ecmaVersion}`];
+    }
+}
+
 /**
  * Ensures that variables representing built-in properties of the Global Object,
  * and any globals declared by special block comments, are present in the global
@@ -240,14 +287,15 @@ function createLintingProblem(options) {
  * Creates a collection of disable directives from a comment
  * @param {Object} options to create disable directives
  * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
- * @param {{line: number, column: number}} options.loc The 0-based location of the comment token
+ * @param {token} options.commentToken The Comment token
  * @param {string} options.value The value after the directive in the comment
  * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
+ * @param {string} options.justification The justification of the directive
  * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules
  * @returns {Object} Directives and problems from the comment
  */
 function createDisableDirectives(options) {
-    const { type, loc, value, ruleMapper } = options;
+    const { commentToken, type, value, justification, ruleMapper } = options;
     const ruleIds = Object.keys(commentParser.parseListConfig(value));
     const directiveRules = ruleIds.length ? ruleIds : [null];
     const result = {
@@ -255,48 +303,80 @@ function createDisableDirectives(options) {
         directiveProblems: [] // problems in directives
     };
 
+    const parentComment = { commentToken, ruleIds };
+
     for (const ruleId of directiveRules) {
 
         // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
-        if (ruleId === null || ruleMapper(ruleId) !== null) {
-            result.directives.push({ type, line: loc.start.line, column: loc.start.column + 1, ruleId });
+        if (ruleId === null || !!ruleMapper(ruleId)) {
+            if (type === "disable-next-line") {
+                result.directives.push({
+                    parentComment,
+                    type,
+                    line: commentToken.loc.end.line,
+                    column: commentToken.loc.end.column + 1,
+                    ruleId,
+                    justification
+                });
+            } else {
+                result.directives.push({
+                    parentComment,
+                    type,
+                    line: commentToken.loc.start.line,
+                    column: commentToken.loc.start.column + 1,
+                    ruleId,
+                    justification
+                });
+            }
         } else {
-            result.directiveProblems.push(createLintingProblem({ ruleId, loc }));
+            result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
         }
     }
     return result;
 }
 
 /**
- * Remove the ignored part from a given directive comment and trim it.
- * @param {string} value The comment text to strip.
- * @returns {string} The stripped text.
+ * Extract the directive and the justification from a given directive comment and trim them.
+ * @param {string} value The comment text to extract.
+ * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
  */
-function stripDirectiveComment(value) {
-    return value.split(/\s-{2,}\s/u)[0].trim();
+function extractDirectiveComment(value) {
+    const match = /\s-{2,}\s/u.exec(value);
+
+    if (!match) {
+        return { directivePart: value.trim(), justificationPart: "" };
+    }
+
+    const directive = value.slice(0, match.index).trim();
+    const justification = value.slice(match.index + match[0].length).trim();
+
+    return { directivePart: directive, justificationPart: justification };
 }
 
 /**
  * Parses comments in file to extract file-specific config of rules, globals
  * and environments and merges them with global config; also code blocks
  * where reporting is disabled or enabled and merges them with reporting config.
- * @param {string} filename The file being checked.
  * @param {ASTNode} ast The top node of the AST.
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
  * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
  * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
  */
-function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
+function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
     const configuredRules = {};
     const enabledGlobals = Object.create(null);
     const exportedVariables = {};
     const problems = [];
     const disableDirectives = [];
+    const validator = new ConfigValidator({
+        builtInRules: Rules
+    });
 
     ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
-        const trimmedCommentText = stripDirectiveComment(comment.value);
-        const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText);
+        const { directivePart, justificationPart } = extractDirectiveComment(comment.value);
+
+        const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(directivePart);
 
         if (!match) {
             return;
@@ -320,7 +400,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
             return;
         }
 
-        if (lineCommentSupported && comment.loc.start.line !== comment.loc.end.line) {
+        if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
             const message = `${directiveText} comment should not span multiple lines.`;
 
             problems.push(createLintingProblem({
@@ -331,7 +411,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
             return;
         }
 
-        const directiveValue = trimmedCommentText.slice(match.index + directiveText.length);
+        const directiveValue = directivePart.slice(match.index + directiveText.length);
 
         switch (directiveText) {
             case "eslint-disable":
@@ -339,7 +419,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
             case "eslint-disable-next-line":
             case "eslint-disable-line": {
                 const directiveType = directiveText.slice("eslint-".length);
-                const options = { type: directiveType, loc: comment.loc, value: directiveValue, ruleMapper };
+                const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
                 const { directives, directiveProblems } = createDisableDirectives(options);
 
                 disableDirectives.push(...directives);
@@ -387,7 +467,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
                         const rule = ruleMapper(name);
                         const ruleValue = parseResult.config[name];
 
-                        if (rule === null) {
+                        if (!rule) {
                             problems.push(createLintingProblem({ ruleId: name, loc: comment.loc }));
                             return;
                         }
@@ -429,10 +509,17 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
 
 /**
  * Normalize ECMAScript version from the initial config
- * @param  {number} ecmaVersion ECMAScript version from the initial config
+ * @param {Parser} parser The parser which uses this options.
+ * @param {number} ecmaVersion ECMAScript version from the initial config
  * @returns {number} normalized ECMAScript version
  */
-function normalizeEcmaVersion(ecmaVersion) {
+function normalizeEcmaVersion(parser, ecmaVersion) {
+
+    if (isEspree(parser)) {
+        if (ecmaVersion === "latest") {
+            return espree.latestEcmaVersion;
+        }
+    }
 
     /*
      * Calculate ECMAScript edition number from official year version starting with
@@ -441,7 +528,39 @@ function normalizeEcmaVersion(ecmaVersion) {
     return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
 }
 
-const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gu;
+/**
+ * Normalize ECMAScript version from the initial config into languageOptions (year)
+ * format.
+ * @param {any} [ecmaVersion] ECMAScript version from the initial config
+ * @returns {number} normalized ECMAScript version
+ */
+function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
+
+    switch (ecmaVersion) {
+        case 3:
+            return 3;
+
+        // void 0 = no ecmaVersion specified so use the default
+        case 5:
+        case void 0:
+            return 5;
+
+        default:
+            if (typeof ecmaVersion === "number") {
+                return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
+            }
+    }
+
+    /*
+     * We default to the latest supported ecmaVersion for everything else.
+     * Remember, this is for languageOptions.ecmaVersion, which sets the version
+     * that is used for a number of processes inside of ESLint. It's normally
+     * safe to assume people want the latest unless otherwise specified.
+     */
+    return espree.latestEcmaVersion + 2009;
+}
+
+const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
 
 /**
  * Checks whether or not there is a comment which has "eslint-env *" in a given text.
@@ -454,10 +573,12 @@ function findEslintEnv(text) {
     eslintEnvPattern.lastIndex = 0;
 
     while ((match = eslintEnvPattern.exec(text)) !== null) {
-        retv = Object.assign(
-            retv || {},
-            commentParser.parseListConfig(stripDirectiveComment(match[1]))
-        );
+        if (match[0].endsWith("*/")) {
+            retv = Object.assign(
+                retv || {},
+                commentParser.parseListConfig(extractDirectiveComment(match[1]).directivePart)
+            );
+        }
     }
 
     return retv;
@@ -490,7 +611,11 @@ function normalizeFilename(filename) {
  * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
  */
 function normalizeVerifyOptions(providedOptions, config) {
-    const disableInlineConfig = config.noInlineConfig === true;
+
+    const linterOptions = config.linterOptions || config;
+
+    // .noInlineConfig for eslintrc, .linterOptions.noInlineConfig for flat
+    const disableInlineConfig = linterOptions.noInlineConfig === true;
     const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
     const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
         ? ` (${config.configNameOfNoInlineConfig})`
@@ -502,7 +627,9 @@ function normalizeVerifyOptions(providedOptions, config) {
         reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
     }
     if (typeof reportUnusedDisableDirectives !== "string") {
-        reportUnusedDisableDirectives = config.reportUnusedDisableDirectives ? "warn" : "off";
+        reportUnusedDisableDirectives =
+            linterOptions.reportUnusedDisableDirectives
+                ? "warn" : "off";
     }
 
     return {
@@ -518,16 +645,17 @@ function normalizeVerifyOptions(providedOptions, config) {
 
 /**
  * Combines the provided parserOptions with the options from environments
- * @param {string} parserName The parser name which uses this options.
+ * @param {Parser} parser The parser which uses this options.
  * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
  * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  * @returns {ParserOptions} Resulting parser options after merge
  */
-function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
+function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
+
     const parserOptionsFromEnv = enabledEnvironments
         .filter(env => env.parserOptions)
-        .reduce((parserOptions, env) => lodash.merge(parserOptions, env.parserOptions), {});
-    const mergedParserOptions = lodash.merge(parserOptionsFromEnv, providedOptions || {});
+        .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
+    const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
     const isModule = mergedParserOptions.sourceType === "module";
 
     if (isModule) {
@@ -539,16 +667,35 @@ function resolveParserOptions(parserName, providedOptions, enabledEnvironments)
         mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
     }
 
-    /*
-     * TODO: @aladdin-add
-     * 1. for a 3rd-party parser, do not normalize parserOptions
-     * 2. for espree, no need to do this (espree will do it)
-     */
-    mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion);
+    mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
 
     return mergedParserOptions;
 }
 
+/**
+ * Converts parserOptions to languageOptions for backwards compatibility with eslintrc.
+ * @param {ConfigData} config Config object.
+ * @param {Object} config.globals Global variable definitions.
+ * @param {Parser} config.parser The parser to use.
+ * @param {ParserOptions} config.parserOptions The parserOptions to use.
+ * @returns {LanguageOptions} The languageOptions equivalent.
+ */
+function createLanguageOptions({ globals: configuredGlobals, parser, parserOptions }) {
+
+    const {
+        ecmaVersion,
+        sourceType
+    } = parserOptions;
+
+    return {
+        globals: configuredGlobals,
+        ecmaVersion: normalizeEcmaVersionForLanguageOptions(ecmaVersion),
+        sourceType,
+        parser,
+        parserOptions
+    };
+}
+
 /**
  * Combines the provided globals object with the globals from environments
  * @param {Record<string, GlobalConf>} providedGlobals The 'globals' key in a config
@@ -597,20 +744,21 @@ function getRuleOptions(ruleConfig) {
 /**
  * Analyze scope of the given AST.
  * @param {ASTNode} ast The `Program` node to analyze.
- * @param {ParserOptions} parserOptions The parser options.
+ * @param {LanguageOptions} languageOptions The parser options.
  * @param {Record<string, string[]>} visitorKeys The visitor keys.
  * @returns {ScopeManager} The analysis result.
  */
-function analyzeScope(ast, parserOptions, visitorKeys) {
+function analyzeScope(ast, languageOptions, visitorKeys) {
+    const parserOptions = languageOptions.parserOptions;
     const ecmaFeatures = parserOptions.ecmaFeatures || {};
-    const ecmaVersion = parserOptions.ecmaVersion || 5;
+    const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
 
     return eslintScope.analyze(ast, {
         ignoreEval: true,
         nodejsScope: ecmaFeatures.globalReturn,
         impliedStrict: ecmaFeatures.impliedStrict,
-        ecmaVersion,
-        sourceType: parserOptions.sourceType || "script",
+        ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
+        sourceType: languageOptions.sourceType || "script",
         childVisitorKeys: visitorKeys || evk.KEYS,
         fallback: Traverser.getKeys
     });
@@ -621,25 +769,29 @@ function analyzeScope(ast, parserOptions, visitorKeys) {
  * optimization of functions, so it's best to keep the try-catch as isolated
  * as possible
  * @param {string} text The text to parse.
- * @param {Parser} parser The parser to parse.
- * @param {ParserOptions} providedParserOptions Options to pass to the parser
+ * @param {LanguageOptions} languageOptions Options to pass to the parser
  * @param {string} filePath The path to the file being parsed.
  * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
  * @private
  */
-function parse(text, parser, providedParserOptions, filePath) {
+function parse(text, languageOptions, filePath) {
     const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
-    const parserOptions = Object.assign({}, providedParserOptions, {
-        loc: true,
-        range: true,
-        raw: true,
-        tokens: true,
-        comment: true,
-        eslintVisitorKeys: true,
-        eslintScopeManager: true,
-        filePath
-    });
+    const { ecmaVersion, sourceType, parser } = languageOptions;
+    const parserOptions = Object.assign(
+        { ecmaVersion, sourceType },
+        languageOptions.parserOptions,
+        {
+            loc: true,
+            range: true,
+            raw: true,
+            tokens: true,
+            comment: true,
+            eslintVisitorKeys: true,
+            eslintScopeManager: true,
+            filePath
+        }
+    );
 
     /*
      * Check for parsing errors first. If there's a parsing error, nothing
@@ -648,13 +800,20 @@ function parse(text, parser, providedParserOptions, filePath) {
      * problem that ESLint identified just like any other.
      */
     try {
+        debug("Parsing:", filePath);
         const parseResult = (typeof parser.parseForESLint === "function")
             ? parser.parseForESLint(textToParse, parserOptions)
             : { ast: parser.parse(textToParse, parserOptions) };
+
+        debug("Parsing successful:", filePath);
         const ast = parseResult.ast;
         const parserServices = parseResult.services || {};
         const visitorKeys = parseResult.visitorKeys || evk.KEYS;
-        const scopeManager = parseResult.scopeManager || analyzeScope(ast, parserOptions, visitorKeys);
+
+        debug("Scope analysis:", filePath);
+        const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
+
+        debug("Scope analysis successful:", filePath);
 
         return {
             success: true,
@@ -723,13 +882,17 @@ function getScope(scopeManager, currentNode) {
  * Marks a variable as used in the current scope
  * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function.
  * @param {ASTNode} currentNode The node currently being traversed
- * @param {Object} parserOptions The options used to parse this text
+ * @param {LanguageOptions} languageOptions The options used to parse this text
  * @param {string} name The name of the variable that should be marked as used.
  * @returns {boolean} True if the variable was found and marked as used, false if not.
  */
-function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
-    const hasGlobalReturn = parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn;
-    const specialScope = hasGlobalReturn || parserOptions.sourceType === "module";
+function markVariableAsUsed(scopeManager, currentNode, languageOptions, name) {
+    const parserOptions = languageOptions.parserOptions;
+    const sourceType = languageOptions.sourceType;
+    const hasGlobalReturn =
+        (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) ||
+        sourceType === "commonjs";
+    const specialScope = hasGlobalReturn || sourceType === "module";
     const currentScope = getScope(scopeManager, currentNode);
 
     // Special Node.js scope means we need to start one level deeper
@@ -751,6 +914,7 @@ function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
  * Runs a rule, and gets its listeners
  * @param {Rule} rule A normalized rule with a `create` method
  * @param {Context} ruleContext The context that should be passed to the rule
+ * @throws {any} Any error during the rule's `create`
  * @returns {Object} A map of selector listeners provided by the rule
  */
 function createRuleListeners(rule, ruleContext) {
@@ -819,15 +983,16 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  * @param {SourceCode} sourceCode A SourceCode object for the given text
  * @param {Object} configuredRules The rules configuration
  * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
- * @param {Object} parserOptions The options that were passed to the parser
- * @param {string} parserName The name of the parser in the config
+ * @param {string | undefined} parserName The name of the parser in the config
+ * @param {LanguageOptions} languageOptions The options for parsing the code.
  * @param {Object} settings The settings that were enabled in the config
  * @param {string} filename The reported filename of the code
  * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
  * @param {string | undefined} cwd cwd of the cli
+ * @param {string} physicalFilename The full path of the file on disk without any code block information
  * @returns {Problem[]} An array of reported problems
  */
-function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd) {
+function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) {
     const emitter = createEmitter();
     const nodeQueue = [];
     let currentNode = sourceCode.ast;
@@ -856,18 +1021,21 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                 getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
                 getCwd: () => cwd,
                 getFilename: () => filename,
+                getPhysicalFilename: () => physicalFilename || filename,
                 getScope: () => getScope(sourceCode.scopeManager, currentNode),
                 getSourceCode: () => sourceCode,
-                markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name),
-                parserOptions,
+                markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, languageOptions, name),
+                parserOptions: {
+                    ...languageOptions.parserOptions
+                },
                 parserPath: parserName,
+                languageOptions,
                 parserServices: sourceCode.parserServices,
                 settings
             }
         )
     );
 
-
     const lintingProblems = [];
 
     Object.keys(configuredRules).forEach(ruleId => {
@@ -880,7 +1048,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
 
         const rule = ruleMapper(ruleId);
 
-        if (rule === null) {
+        if (!rule) {
             lintingProblems.push(createLintingProblem({ ruleId }));
             return;
         }
@@ -916,8 +1084,16 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                         }
                         const problem = reportTranslator(...args);
 
-                        if (problem.fix && rule.meta && !rule.meta.fixable) {
-                            throw new Error("Fixable rules should export a `meta.fixable` property.");
+                        if (problem.fix && !(rule.meta && rule.meta.fixable)) {
+                            throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
+                        }
+                        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);
                     }
@@ -925,20 +1101,45 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
             )
         );
 
-        const ruleListeners = createRuleListeners(rule, ruleContext);
+        const ruleListeners = timing.enabled ? timing.time(ruleId, createRuleListeners)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
+
+        /**
+         * Include `ruleId` in error logs
+         * @param {Function} ruleListener A rule method that listens for a node.
+         * @returns {Function} ruleListener wrapped in error handler
+         */
+        function addRuleErrorHandler(ruleListener) {
+            return function ruleErrorHandler(...listenerArgs) {
+                try {
+                    return ruleListener(...listenerArgs);
+                } catch (e) {
+                    e.ruleId = ruleId;
+                    throw e;
+                }
+            };
+        }
+
+        if (typeof ruleListeners === "undefined" || ruleListeners === null) {
+            throw new Error(`The create() function for rule '${ruleId}' did not return an object.`);
+        }
 
         // add all the selectors from the rule as listeners
         Object.keys(ruleListeners).forEach(selector => {
+            const ruleListener = timing.enabled
+                ? timing.time(ruleId, ruleListeners[selector])
+                : ruleListeners[selector];
+
             emitter.on(
                 selector,
-                timing.enabled
-                    ? timing.time(ruleId, ruleListeners[selector])
-                    : ruleListeners[selector]
+                addRuleErrorHandler(ruleListener)
             );
         });
     });
 
-    const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
+    // only run code path analyzer if the top level node is "Program", skip otherwise
+    const eventGenerator = nodeQueue[0].node.type === "Program"
+        ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
+        : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
 
     nodeQueue.forEach(traversalInfo => {
         currentNode = traversalInfo.node;
@@ -1015,7 +1216,7 @@ function normalizeCwd(cwd) {
     }
 
     // It's more explicit to assign the undefined
-    // eslint-disable-next-line no-undefined
+    // eslint-disable-next-line no-undefined -- Consistently returning a value
     return undefined;
 }
 
@@ -1025,26 +1226,44 @@ function normalizeCwd(cwd) {
  */
 const internalSlotsMap = new WeakMap();
 
+/**
+ * Throws an error when the given linter is in flat config mode.
+ * @param {Linter} linter The linter to check.
+ * @returns {void}
+ * @throws {Error} If the linter is in flat config mode.
+ */
+function assertEslintrcConfig(linter) {
+    const { configType } = internalSlotsMap.get(linter);
+
+    if (configType === "flat") {
+        throw new Error("This method cannot be used with flat config. Add your entries directly into the config array.");
+    }
+}
+
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
 
 /**
  * Object that is responsible for verifying JavaScript text
- * @name eslint
+ * @name Linter
  */
 class Linter {
 
     /**
      * Initialize the Linter.
      * @param {Object} [config] the config object
-     * @param {string} [config.cwd]  path to a directory that should be considered as the current working directory, can be undefined.
+     * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
+     * @param {"flat"|"eslintrc"} [config.configType="eslintrc"] the type of config used.
      */
-    constructor({ cwd } = {}) {
+    constructor({ cwd, configType } = {}) {
         internalSlotsMap.set(this, {
             cwd: normalizeCwd(cwd),
             lastConfigArray: null,
             lastSourceCode: null,
+            lastSuppressedMessages: [],
+            configType, // TODO: Remove after flat config conversion
             parserMap: new Map([["espree", espree]]),
             ruleMap: new Rules()
         });
@@ -1066,7 +1285,8 @@ class Linter {
      * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
      * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
      * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
-     * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
+     * @throws {Error} If during rule execution.
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
      */
     _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
         const slots = internalSlotsMap.get(this);
@@ -1115,15 +1335,19 @@ class Linter {
             .map(envName => getEnv(slots, envName))
             .filter(env => env);
 
-        const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs);
+        const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
         const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
         const settings = config.settings || {};
+        const languageOptions = createLanguageOptions({
+            globals: config.globals,
+            parser,
+            parserOptions
+        });
 
         if (!slots.lastSourceCode) {
             const parseResult = parse(
                 text,
-                parser,
-                parserOptions,
+                languageOptions,
                 options.filename
             );
 
@@ -1144,14 +1368,14 @@ class Linter {
                     ast: slots.lastSourceCode.ast,
                     parserServices: slots.lastSourceCode.parserServices,
                     visitorKeys: slots.lastSourceCode.visitorKeys,
-                    scopeManager: analyzeScope(slots.lastSourceCode.ast, parserOptions)
+                    scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
                 });
             }
         }
 
         const sourceCode = slots.lastSourceCode;
         const commentDirectives = options.allowInlineConfig
-            ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
+            ? getDirectiveComments(sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
             : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
 
         // augment global scope with declared global variables
@@ -1170,12 +1394,13 @@ class Linter {
                 sourceCode,
                 configuredRules,
                 ruleId => getRule(slots, ruleId),
-                parserOptions,
                 parserName,
+                languageOptions,
                 settings,
                 options.filename,
                 options.disableFixes,
-                slots.cwd
+                slots.cwd,
+                providedOptions.physicalFilename
             );
         } catch (err) {
             err.message += `\nOccurred while linting ${options.filename}`;
@@ -1190,11 +1415,17 @@ class Linter {
             debug("Parser Options:", parserOptions);
             debug("Parser Path:", parserName);
             debug("Settings:", settings);
+
+            if (err.ruleId) {
+                err.message += `\nRule: "${err.ruleId}"`;
+            }
+
             throw err;
         }
 
         return applyDisableDirectives({
             directives: commentDirectives.disableDirectives,
+            disableFixes: options.disableFixes,
             problems: lintingProblems
                 .concat(commentDirectives.problems)
                 .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
@@ -1213,23 +1444,287 @@ class Linter {
      */
     verify(textOrSourceCode, config, filenameOrOptions) {
         debug("Verify");
+
+        const { configType } = internalSlotsMap.get(this);
+
         const options = typeof filenameOrOptions === "string"
             ? { filename: filenameOrOptions }
             : filenameOrOptions || {};
 
-        // CLIEngine passes a `ConfigArray` object.
-        if (config && typeof config.extractConfig === "function") {
-            return this._verifyWithConfigArray(textOrSourceCode, config, options);
+        if (config) {
+            if (configType === "flat") {
+
+                /*
+                 * Because of how Webpack packages up the files, we can't
+                 * compare directly to `FlatConfigArray` using `instanceof`
+                 * because it's not the same `FlatConfigArray` as in the tests.
+                 * So, we work around it by assuming an array is, in fact, a
+                 * `FlatConfigArray` if it has a `getConfig()` method.
+                 */
+                let configArray = config;
+
+                if (!Array.isArray(config) || typeof config.getConfig !== "function") {
+                    configArray = new FlatConfigArray(config);
+                    configArray.normalizeSync();
+                }
+
+                return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true));
+            }
+
+            if (typeof config.extractConfig === "function") {
+                return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, config, options));
+            }
         }
 
+        /*
+         * If we get to here, it means `config` is just an object rather
+         * than a config array so we can go right into linting.
+         */
+
         /*
          * `Linter` doesn't support `overrides` property in configuration.
          * So we cannot apply multiple processors.
          */
         if (options.preprocess || options.postprocess) {
-            return this._verifyWithProcessor(textOrSourceCode, config, options);
+            return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, config, options));
         }
-        return this._verifyWithoutProcessors(textOrSourceCode, config, options);
+        return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, config, options));
+    }
+
+    /**
+     * Verify with a processor.
+     * @param {string|SourceCode} textOrSourceCode The source code.
+     * @param {FlatConfig} config The config array.
+     * @param {VerifyOptions&ProcessorOptions} options The options.
+     * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
+     */
+    _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) {
+        const filename = options.filename || "<input>";
+        const filenameToExpose = normalizeFilename(filename);
+        const physicalFilename = options.physicalFilename || filenameToExpose;
+        const text = ensureText(textOrSourceCode);
+        const preprocess = options.preprocess || (rawText => [rawText]);
+        const postprocess = options.postprocess || (messagesList => messagesList.flat());
+        const filterCodeBlock =
+            options.filterCodeBlock ||
+            (blockFilename => blockFilename.endsWith(".js"));
+        const originalExtname = path.extname(filename);
+
+        let blocks;
+
+        try {
+            blocks = preprocess(text, filenameToExpose);
+        } catch (ex) {
+
+            // If the message includes a leading line number, strip it:
+            const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
+
+            debug("%s\n%s", message, ex.stack);
+
+            return [
+                {
+                    ruleId: null,
+                    fatal: true,
+                    severity: 2,
+                    message,
+                    line: ex.lineNumber,
+                    column: ex.column
+                }
+            ];
+        }
+
+        const messageLists = blocks.map((block, i) => {
+            debug("A code block was found: %o", block.filename || "(unnamed)");
+
+            // Keep the legacy behavior.
+            if (typeof block === "string") {
+                return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options);
+            }
+
+            const blockText = block.text;
+            const blockName = path.join(filename, `${i}_${block.filename}`);
+
+            // Skip this block if filtered.
+            if (!filterCodeBlock(blockName, blockText)) {
+                debug("This code block was skipped.");
+                return [];
+            }
+
+            // Resolve configuration again if the file content or extension was changed.
+            if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
+                debug("Resolving configuration again because the file content or extension was changed.");
+                return this._verifyWithFlatConfigArray(
+                    blockText,
+                    configForRecursive,
+                    { ...options, filename: blockName, physicalFilename }
+                );
+            }
+
+            // Does lint.
+            return this._verifyWithFlatConfigArrayAndWithoutProcessors(
+                blockText,
+                config,
+                { ...options, filename: blockName, physicalFilename }
+            );
+        });
+
+        return postprocess(messageLists, filenameToExpose);
+    }
+
+    /**
+     * Same as linter.verify, except without support for processors.
+     * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
+     * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
+     * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
+     * @throws {Error} If during rule execution.
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
+     */
+    _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
+        const slots = internalSlotsMap.get(this);
+        const config = providedConfig || {};
+        const options = normalizeVerifyOptions(providedOptions, config);
+        let text;
+
+        // evaluate arguments
+        if (typeof textOrSourceCode === "string") {
+            slots.lastSourceCode = null;
+            text = textOrSourceCode;
+        } else {
+            slots.lastSourceCode = textOrSourceCode;
+            text = textOrSourceCode.text;
+        }
+
+        const languageOptions = config.languageOptions;
+
+        languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
+            languageOptions.ecmaVersion
+        );
+
+        // add configured globals and language globals
+        const configuredGlobals = {
+            ...(getGlobalsForEcmaVersion(languageOptions.ecmaVersion)),
+            ...(languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0),
+            ...languageOptions.globals
+        };
+
+        // double check that there is a parser to avoid mysterious error messages
+        if (!languageOptions.parser) {
+            throw new TypeError(`No parser specified for ${options.filename}`);
+        }
+
+        // Espree expects this information to be passed in
+        if (isEspree(languageOptions.parser)) {
+            const parserOptions = languageOptions.parserOptions;
+
+            if (languageOptions.sourceType) {
+
+                parserOptions.sourceType = languageOptions.sourceType;
+
+                if (
+                    parserOptions.sourceType === "module" &&
+                    parserOptions.ecmaFeatures &&
+                    parserOptions.ecmaFeatures.globalReturn
+                ) {
+                    parserOptions.ecmaFeatures.globalReturn = false;
+                }
+            }
+        }
+
+        const settings = config.settings || {};
+
+        if (!slots.lastSourceCode) {
+            const parseResult = parse(
+                text,
+                languageOptions,
+                options.filename
+            );
+
+            if (!parseResult.success) {
+                return [parseResult.error];
+            }
+
+            slots.lastSourceCode = parseResult.sourceCode;
+        } else {
+
+            /*
+             * If the given source code object as the first argument does not have scopeManager, analyze the scope.
+             * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
+             */
+            if (!slots.lastSourceCode.scopeManager) {
+                slots.lastSourceCode = new SourceCode({
+                    text: slots.lastSourceCode.text,
+                    ast: slots.lastSourceCode.ast,
+                    parserServices: slots.lastSourceCode.parserServices,
+                    visitorKeys: slots.lastSourceCode.visitorKeys,
+                    scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
+                });
+            }
+        }
+
+        const sourceCode = slots.lastSourceCode;
+        const commentDirectives = options.allowInlineConfig
+            ? getDirectiveComments(
+                sourceCode.ast,
+                ruleId => getRuleFromConfig(ruleId, config),
+                options.warnInlineConfig
+            )
+            : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
+
+        // augment global scope with declared global variables
+        addDeclaredGlobals(
+            sourceCode.scopeManager.scopes[0],
+            configuredGlobals,
+            { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
+        );
+
+        const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
+
+        let lintingProblems;
+
+        try {
+            lintingProblems = runRules(
+                sourceCode,
+                configuredRules,
+                ruleId => getRuleFromConfig(ruleId, config),
+                void 0,
+                languageOptions,
+                settings,
+                options.filename,
+                options.disableFixes,
+                slots.cwd,
+                providedOptions.physicalFilename
+            );
+        } catch (err) {
+            err.message += `\nOccurred while linting ${options.filename}`;
+            debug("An error occurred while traversing");
+            debug("Filename:", options.filename);
+            if (err.currentNode) {
+                const { line } = err.currentNode.loc.start;
+
+                debug("Line:", line);
+                err.message += `:${line}`;
+            }
+            debug("Parser Options:", languageOptions.parserOptions);
+
+            // debug("Parser Path:", parserName);
+            debug("Settings:", settings);
+
+            if (err.ruleId) {
+                err.message += `\nRule: "${err.ruleId}"`;
+            }
+
+            throw err;
+        }
+
+        return applyDisableDirectives({
+            directives: commentDirectives.disableDirectives,
+            disableFixes: options.disableFixes,
+            problems: lintingProblems
+                .concat(commentDirectives.problems)
+                .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
+            reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
+        });
     }
 
     /**
@@ -1237,7 +1732,7 @@ class Linter {
      * @param {string|SourceCode} textOrSourceCode The source code.
      * @param {ConfigArray} configArray The config array.
      * @param {VerifyOptions&ProcessorOptions} options The options.
-     * @returns {LintMessage[]} The found problems.
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
      */
     _verifyWithConfigArray(textOrSourceCode, configArray, options) {
         debug("With ConfigArray: %s", options.filename);
@@ -1267,25 +1762,103 @@ class Linter {
         return this._verifyWithoutProcessors(textOrSourceCode, config, options);
     }
 
+    /**
+     * Verify a given code with a flat config.
+     * @param {string|SourceCode} textOrSourceCode The source code.
+     * @param {FlatConfigArray} configArray The config array.
+     * @param {VerifyOptions&ProcessorOptions} options The options.
+     * @param {boolean} [firstCall=false] Indicates if this is being called directly
+     *      from verify(). (TODO: Remove once eslintrc is removed.)
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
+     */
+    _verifyWithFlatConfigArray(textOrSourceCode, configArray, options, firstCall = false) {
+        debug("With flat config: %s", options.filename);
+
+        // we need a filename to match configs against
+        const filename = options.filename || "__placeholder__.js";
+
+        // Store the config array in order to get plugin envs and rules later.
+        internalSlotsMap.get(this).lastConfigArray = configArray;
+        const config = configArray.getConfig(filename);
+
+        if (!config) {
+            return [
+                {
+                    ruleId: null,
+                    severity: 1,
+                    message: `No matching configuration found for ${filename}.`,
+                    line: 0,
+                    column: 0
+                }
+            ];
+        }
+
+        // Verify.
+        if (config.processor) {
+            debug("Apply the processor: %o", config.processor);
+            const { preprocess, postprocess, supportsAutofix } = config.processor;
+            const disableFixes = options.disableFixes || !supportsAutofix;
+
+            return this._verifyWithFlatConfigArrayAndProcessor(
+                textOrSourceCode,
+                config,
+                { ...options, filename, disableFixes, postprocess, preprocess },
+                configArray
+            );
+        }
+
+        // check for options-based processing
+        if (firstCall && (options.preprocess || options.postprocess)) {
+            return this._verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options);
+        }
+
+        return this._verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, config, options);
+    }
+
     /**
      * Verify with a processor.
      * @param {string|SourceCode} textOrSourceCode The source code.
      * @param {ConfigData|ExtractedConfig} config The config array.
      * @param {VerifyOptions&ProcessorOptions} options The options.
      * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
-     * @returns {LintMessage[]} The found problems.
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
      */
     _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
         const filename = options.filename || "<input>";
         const filenameToExpose = normalizeFilename(filename);
+        const physicalFilename = options.physicalFilename || filenameToExpose;
         const text = ensureText(textOrSourceCode);
         const preprocess = options.preprocess || (rawText => [rawText]);
-        const postprocess = options.postprocess || lodash.flatten;
+        const postprocess = options.postprocess || (messagesList => messagesList.flat());
         const filterCodeBlock =
             options.filterCodeBlock ||
             (blockFilename => blockFilename.endsWith(".js"));
         const originalExtname = path.extname(filename);
-        const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
+
+        let blocks;
+
+        try {
+            blocks = preprocess(text, filenameToExpose);
+        } catch (ex) {
+
+            // If the message includes a leading line number, strip it:
+            const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
+
+            debug("%s\n%s", message, ex.stack);
+
+            return [
+                {
+                    ruleId: null,
+                    fatal: true,
+                    severity: 2,
+                    message,
+                    line: ex.lineNumber,
+                    column: ex.column
+                }
+            ];
+        }
+
+        const messageLists = blocks.map((block, i) => {
             debug("A code block was found: %o", block.filename || "(unnamed)");
 
             // Keep the legacy behavior.
@@ -1302,13 +1875,13 @@ class Linter {
                 return [];
             }
 
-            // Resolve configuration again if the file extension was changed.
-            if (configForRecursive && path.extname(blockName) !== originalExtname) {
-                debug("Resolving configuration again because the file extension was changed.");
+            // Resolve configuration again if the file content or extension was changed.
+            if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
+                debug("Resolving configuration again because the file content or extension was changed.");
                 return this._verifyWithConfigArray(
                     blockText,
                     configForRecursive,
-                    { ...options, filename: blockName }
+                    { ...options, filename: blockName, physicalFilename }
                 );
             }
 
@@ -1316,13 +1889,37 @@ class Linter {
             return this._verifyWithoutProcessors(
                 blockText,
                 config,
-                { ...options, filename: blockName }
+                { ...options, filename: blockName, physicalFilename }
             );
         });
 
         return postprocess(messageLists, filenameToExpose);
     }
 
+    /**
+     * Given a list of reported problems, distinguish problems between normal messages and suppressed messages.
+     * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages.
+     * @param {Problem[]} problems A list of reported problems.
+     * @returns {LintMessage[]} A list of LintMessage.
+     */
+    _distinguishSuppressedMessages(problems) {
+        const messages = [];
+        const suppressedMessages = [];
+        const slots = internalSlotsMap.get(this);
+
+        for (const problem of problems) {
+            if (problem.suppressions) {
+                suppressedMessages.push(problem);
+            } else {
+                messages.push(problem);
+            }
+        }
+
+        slots.lastSuppressedMessages = suppressedMessages;
+
+        return messages;
+    }
+
     /**
      * Gets the SourceCode object representing the parsed source.
      * @returns {SourceCode} The SourceCode object.
@@ -1331,6 +1928,14 @@ class Linter {
         return internalSlotsMap.get(this).lastSourceCode;
     }
 
+    /**
+     * Gets the list of SuppressedLintMessage produced in the last running.
+     * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
+     */
+    getSuppressedMessages() {
+        return internalSlotsMap.get(this).lastSuppressedMessages;
+    }
+
     /**
      * Defines a new linting rule.
      * @param {string} ruleId A unique rule identifier
@@ -1338,6 +1943,7 @@ class Linter {
      * @returns {void}
      */
     defineRule(ruleId, ruleModule) {
+        assertEslintrcConfig(this);
         internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule);
     }
 
@@ -1347,6 +1953,7 @@ class Linter {
      * @returns {void}
      */
     defineRules(rulesToDefine) {
+        assertEslintrcConfig(this);
         Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
             this.defineRule(ruleId, rulesToDefine[ruleId]);
         });
@@ -1357,6 +1964,7 @@ class Linter {
      * @returns {Map<string, Rule>} All loaded rules
      */
     getRules() {
+        assertEslintrcConfig(this);
         const { lastConfigArray, ruleMap } = internalSlotsMap.get(this);
 
         return new Map(function *() {
@@ -1375,6 +1983,7 @@ class Linter {
      * @returns {void}
      */
     defineParser(parserId, parserModule) {
+        assertEslintrcConfig(this);
         internalSlotsMap.get(this).parserMap.set(parserId, parserModule);
     }
 
@@ -1382,7 +1991,7 @@ class Linter {
      * Performs multiple autofix passes over the text until as many fixes as possible
      * have been applied.
      * @param {string} text The source text to apply fixes to.
-     * @param {ConfigData|ConfigArray} config The ESLint config object to use.
+     * @param {ConfigData|ConfigArray|FlatConfigArray} config The ESLint config object to use.
      * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use.
      * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the
      *      SourceCodeFixer.